Ticket #9: visualreplay-WIP-r16530.patch

File visualreplay-WIP-r16530.patch, 20.2 KB (added by elexis, 9 years ago)

This patch replays a commands.txt visually with the -repay-visual=/path/to/commands.txt argument. Contrary to earlier patches, this one uses the observer mode, so that the HUD is visible and the viewer can't issue commands while replaying. Also it doesn't change the DEFAULT_TURN_LENGTH_SP to 500, but rather supports variable turn lengths by reading the turn length from the replay and updating it each turn. It seems to work fine already. However to make sure that the replay shows the actual recorded game, further testing is necessary. I thought it might be feasible to check for oos by reading the hash values from the replay file too and comparing them to the hash of the simulation state. However I get an out of sync on each turn with this version. Help welcome.

  • source/network/NetTurnManager.cpp

     
    455455        bool ok = m_Simulation2.ComputeStateHash(hash);
    456456        ENSURE(ok);
    457457    }
    458458    m_Replay.Hash(hash);
    459459#endif
    460460}
    461461
    462462void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
    463463{
    464464    debug_warn(L"This should never be called");
    465465}
    466466
     467CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
     468    CNetLocalTurnManager(simulation, replay)
     469{
     470}
     471
     472void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
     473{
     474    m_ReplayCommands[turn][player].push_back(command);
     475}
     476
     477void CNetReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick)
     478{
     479    m_ReplayHash[turn] = std::make_pair(hash, quick);
     480}
     481
     482void CNetReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength)
     483{
     484    debug_printf("Setting turn %d length to %d\n", turn, turnLength);
     485    m_ReplayTurnLengths[turn] = turnLength;
     486}
    467487
     488void CNetReplayTurnManager::StoreFinalReplayTurn(u32 turn)
     489{
     490    m_FinalReplayTurn = turn;
     491}
     492void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
     493{
     494    // TODO: replay starts with turn 0, not 1
     495    turn--;
    468496
     497    // TODO: this also doesnt seem right, should be > instead of >= but goes one element too far otherwise
     498    if (turn >= m_FinalReplayTurn)
     499        return;
     500
     501    DoTurn(turn);
     502
     503    // compare hash, if it exists in the replay
     504    if (m_ReplayHash.count(turn) == 0)
     505        return;
     506
     507    std::string hash = m_ReplayHash[turn].first;
     508    bool quick = m_ReplayHash[turn].second;
     509
     510    // compute hash
     511    std::string expectedHash;
     512    bool ok = m_Simulation2.ComputeStateHash(expectedHash, quick);
     513    expectedHash = Hexify(expectedHash);
     514    ENSURE(ok);
     515
     516    // TODO: currently an OOS occurs on every turn
     517    // debug_printf("Comparing hash %s with %s\n", expectedHash.c_str(), hash.c_str());
     518
     519    if (hash != expectedHash)
     520    {
     521        // TODO: THROW ERROR MESSAGE, send net message?
     522    }
     523
     524}
     525
     526void CNetReplayTurnManager::DoTurn(u32 turn)
     527{
     528    // set turn length
     529    m_TurnLength = m_ReplayTurnLengths[turn];
     530
     531    // debug_printf("For turn %d setting turn length to %d\n", turn, m_TurnLength);
     532
     533    // add commands
     534    std::map<int, std::vector<std::string> > playerCommands = m_ReplayCommands[turn];
     535    std::map<int, std::vector<std::string> >::iterator it;
     536    for (it = playerCommands.begin(); it != playerCommands.end(); ++it)
     537    {
     538        int player = it->first;
     539        for (size_t i = 0; i < it->second.size(); ++i)
     540        {
     541            JS::RootedValue data(m_Simulation2.GetScriptInterface().GetContext());
     542            m_Simulation2.GetScriptInterface().ParseJSON(it->second[i], &data);
     543            AddCommand(m_ClientId, player, data, m_CurrentTurn + 1);
     544        }
     545    }
     546}
    469547
    470548CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
    471549    m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP)
    472550{
    473551    // The first turn we will actually execute is number 2,
    474552    // so store dummy values into the saved lengths list
    475553    m_SavedTurnLengths.push_back(0);
    476554    m_SavedTurnLengths.push_back(0);
    477555}
    478556
    479557void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
    480558{
  • source/network/NetTurnManager.h

     
    1313 *
    1414 * You should have received a copy of the GNU General Public License
    1515 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
    1616 */
    1717
    1818#ifndef INCLUDED_NETTURNMANAGER
    1919#define INCLUDED_NETTURNMANAGER
    2020
    2121#include "simulation2/helpers/SimulationCommand.h"
    2222
    2323#include <list>
    2424#include <map>
    25 
     25#include <vector>
    2626class CNetServerWorker;
    2727class CNetClient;
    2828class CSimulationMessage;
    2929class CSimulation2;
    3030class IReplayLogger;
    3131
    3232/*
    3333 * This file deals with the logic of the network turn system. The basic idea is as in
    3434 * http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php?print=1
    3535 *
    3636 * Each player performs the simulation for turn N.
    3737 * User input is translated into commands scheduled for execution in turn N+2 which are
     
    180180
    181181    bool m_HasSyncError;
    182182
    183183    IReplayLogger& m_Replay;
    184184
    185185private:
    186186    size_t m_TimeWarpNumTurns; // 0 if disabled
    187187    std::list<std::string> m_TimeWarpStates;
    188188    std::string m_QuickSaveState; // TODO: should implement a proper disk-based quicksave system
    189189    std::string m_QuickSaveMetadata;
    190190};
    191191
     192
    192193/**
    193194 * Implementation of CNetTurnManager for network clients.
    194195 */
    195196class CNetClientTurnManager : public CNetTurnManager
    196197{
    197198public:
    198199    CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay);
    199200
    200201    virtual void OnSimulationMessage(CSimulationMessage* msg);
    201202
    202203    virtual void PostCommand(JS::HandleValue data);
    203204   
     
    224225
    225226    virtual void OnSimulationMessage(CSimulationMessage* msg);
    226227
    227228    virtual void PostCommand(JS::HandleValue data);
    228229
    229230protected:
    230231    virtual void NotifyFinishedOwnCommands(u32 turn);
    231232
    232233    virtual void NotifyFinishedUpdate(u32 turn);
    233234};
    234235
    235236
     237
     238/**
     239 * Implementation of CNetTurnManager for replay games.
     240 */
     241class CNetReplayTurnManager : public CNetLocalTurnManager
     242{
     243public:
     244    CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
     245
     246    void StoreReplayCommand(u32 turn, int player, const std::string& command);
     247
     248    void StoreReplayTurnLength(u32 turn, u32 turnLength);
     249
     250    void StoreReplayHash(u32 turn, const std::string& hash, bool quick);
     251
     252    void StoreFinalReplayTurn(u32 turn);
     253
     254
     255protected:
     256    virtual void NotifyFinishedUpdate(u32 turn);
     257
     258    void DoTurn(u32 turn);
     259
     260    // Contains the commands of every player on each turn
     261    std::map<u32, std::map<int, std::vector<std::string> > > m_ReplayCommands;
     262
     263    // Contains the length of every turn
     264    std::map<u32, u32 > m_ReplayTurnLengths;
     265
     266    // Contains all replay hash values and weather or not the quick hash method was used
     267    std::map<u32, std::pair<std::string, bool> > m_ReplayHash;
     268
     269    // The number of the last turn in the replay
     270    u32 m_FinalReplayTurn;
     271
     272};
    236273/**
    237274 * The server-side counterpart to CNetClientTurnManager.
    238275 * Records the turn state of each client, and sends turn advancement messages
    239276 * when all clients are ready.
    240277 *
    241278 * Thread-safety:
    242279 * - This is constructed and used by CNetServerWorker in the network server thread.
    243280 */
    244281class CNetServerTurnManager
    245282{
    246283    NONCOPYABLE(CNetServerTurnManager);
    247284public:
  • source/ps/Game.cpp

     
    5454extern bool g_GameRestarted;
    5555extern GameLoopState* g_AtlasGameLoop;
    5656
    5757/**
    5858 * Globally accessible pointer to the CGame object.
    5959 **/
    6060CGame *g_Game=NULL;
    6161
    6262/**
    6363 * Constructor
    6464 *
    6565 **/
    66 CGame::CGame(bool disableGraphics):
     66CGame::CGame(bool disableGraphics, bool replayLog):
    6767    m_World(new CWorld(this)),
    6868    m_Simulation2(new CSimulation2(&m_World->GetUnitManager(), g_ScriptRuntime, m_World->GetTerrain())),
    6969    m_GameView(disableGraphics ? NULL : new CGameView(this)),
    7070    m_GameStarted(false),
    7171    m_Paused(false),
    7272    m_SimRate(1.0f),
    7373    m_PlayerID(-1),
    74     m_IsSavedGame(false)
     74    m_IsSavedGame(false),
     75    m_IsReplay(false),
     76    m_ReplayStream(NULL)
    7577{
    76     m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
    7778    // TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps?
     79    if (replayLog)
     80        m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
     81    else
     82        m_ReplayLogger = new CDummyReplayLogger();
    7883
    7984    // Need to set the CObjectManager references after various objects have
    8085    // been initialised, so do it here rather than via the initialisers above.
    8186    if (m_GameView)
    8287        m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager());
    8388
    8489    m_TurnManager = new CNetLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client
    8590
    8691    m_Simulation2->LoadDefaultScripts();
    8792}
     93int CGame::LoadReplayData()
     94{
     95    ENSURE(m_IsReplay);
     96    ENSURE(!m_ReplayPath.empty());
     97
     98    CNetReplayTurnManager* replayTurnMgr = static_cast<CNetReplayTurnManager*>(GetTurnManager());
    8899
     100    u32 currentTurn = 0;
     101    std::string type;
     102    while ((*m_ReplayStream >> type).good())
     103    {
     104        if (type == "turn")
     105        {
     106            u32 turn = 0;
     107            u32 turnLength = 0;
     108            *m_ReplayStream >> turn >> turnLength;
     109            ENSURE(turn == currentTurn);
     110            replayTurnMgr->StoreReplayTurnLength(currentTurn, turnLength);
     111        }
     112        else if (type == "cmd")
     113        {
     114            int player;
     115            *m_ReplayStream >> player;
     116
     117            std::string line;
     118            std::getline(*m_ReplayStream, line);
     119            replayTurnMgr->StoreReplayCommand(currentTurn, player, line);
     120        }
     121        else if (type == "hash" || type == "hash-quick")
     122        {
     123            bool quick = (type == "hash-quick");
     124            std::string replayHash;
     125            *m_ReplayStream >> replayHash;
     126            replayTurnMgr->StoreReplayHash(currentTurn, replayHash, quick);
     127        }
     128        else if (type == "end")
     129        {
     130            currentTurn++;
     131        }
     132        else
     133        {
     134            CancelLoad(L"Failed to load replay data (unrecognized content)");
     135        }
     136    }
     137    m_FinalReplayTurn = currentTurn + 1;
     138    replayTurnMgr->StoreFinalReplayTurn(currentTurn);
     139    return 0;
     140}
     141void CGame::StartReplay(const std::string& replayPath)
     142{
     143    m_IsReplay = true;
     144    ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface();
     145
     146    SetTurnManager(new CNetReplayTurnManager(*m_Simulation2, GetReplayLogger()));
     147
     148    // TODO: IF FILE m_ReplayPath NOT EXISTS PRINT ERROR
     149
     150    m_ReplayPath = replayPath;
     151    m_ReplayStream = new std::ifstream(m_ReplayPath.c_str());
     152    if (!m_ReplayStream->good())
     153    {
     154        debug_printf("Could not open replay file!\n");
     155        //TODO: how to exit properly? std::exit(0);?
     156        ENSURE(m_ReplayStream->good());
     157    }
     158
     159    std::string type;
     160    ENSURE((*m_ReplayStream >> type).good() && type == "start");
     161
     162    std::string line;
     163    std::getline(*m_ReplayStream, line);
     164    JS::RootedValue attribs(scriptInterface.GetContext());
     165    scriptInterface.ParseJSON(line, &attribs);
     166    StartGame(&attribs, "");
     167}
    89168/**
    90169 * Destructor
    91170 *
    92171 **/
    93172CGame::~CGame()
    94173{
    95174    // Again, the in-game call tree is going to be different to the main menu one.
    96175    if (CProfileManager::IsInitialised())
    97176        g_Profiler.StructuralReset();
    98177
    99178    delete m_TurnManager;
    100179    delete m_GameView;
    101180    delete m_Simulation2;
    102181    delete m_World;
    103182    delete m_ReplayLogger;
     183    delete m_ReplayStream;
    104184}
    105185
    106186void CGame::SetTurnManager(CNetTurnManager* turnManager)
    107187{
    108188    if (m_TurnManager)
    109189        delete m_TurnManager;
    110190
    111191    m_TurnManager = turnManager;
    112192
    113193    if (m_TurnManager)
    114194        m_TurnManager->SetPlayerID(m_PlayerID);
    115195}
     
    167247        JS::RootedValue settings(cx);
    168248        scriptInterface.GetProperty(attribs, "map", mapFile);
    169249        scriptInterface.GetProperty(attribs, "settings", &settings);
    170250
    171251        m_World->RegisterInit(mapFile, scriptInterface.GetJSRuntime(), settings, m_PlayerID);
    172252    }
    173253    if (m_GameView)
    174254        RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80);
    175255
    176256    if (m_IsSavedGame)
    177257        RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000);
    178258
     259    if (m_IsReplay)
     260        RegMemFun(this, &CGame::LoadReplayData, L"Loading replay data", 1000);
     261
    179262    LDR_EndRegistering();
    180263}
    181264
    182265int CGame::LoadInitialState()
    183266{
    184267    ENSURE(m_IsSavedGame);
    185268    ENSURE(!m_InitialSavedState.empty());
    186269
    187270    std::string state;
    188271    m_InitialSavedState.swap(state); // deletes the original to save a bit of memory
    189272
    190273    std::stringstream stream(state);
     
    263346    return m_PlayerID;
    264347}
    265348
    266349void CGame::SetPlayerID(int playerID)
    267350{
    268351    m_PlayerID = playerID;
    269352    if (m_TurnManager)
    270353        m_TurnManager->SetPlayerID(m_PlayerID);
    271354}
    272355
    273356void CGame::StartGame(JS::MutableHandleValue attribs, const std::string& savedState)
    274357{
     358    if (m_ReplayLogger != false)
    275359    m_ReplayLogger->StartGame(attribs);
     360
    276361    RegisterInit(attribs, savedState);
    277362}
    278363
    279364// TODO: doInterpolate is optional because Atlas interpolates explicitly,
    280365// so that it has more control over the update rate. The game might want to
    281366// do the same, and then doInterpolate should be redundant and removed.
    282367
    283368bool CGame::Update(const double deltaRealTime, bool doInterpolate)
    284369{
    285370    if (m_Paused)
    286371        return true;
    287372
     
    301386        // At the normal sim rate, we currently want to render at least one
    302387        // frame per simulation turn, so let maxTurns be 1. But for fast-forward
    303388        // sim rates we want to allow more, so it's not bounded by framerate,
    304389        // so just use the sim rate itself as the number of turns per frame.
    305390        size_t maxTurns = (size_t)m_SimRate;
    306391
    307392        if (m_TurnManager->Update(deltaSimTime, maxTurns))
    308393        {
    309394            {
    310395                PROFILE3("gui sim update");
    311396                g_GUI->SendEventToAll("SimulationUpdate");
    312397            }
     398             if (m_IsReplay && m_TurnManager->GetCurrentTurn() == m_FinalReplayTurn)
     399                 g_GUI->SendEventToAll("ReplayFinished");
    313400
    314401            GetView()->GetLOSTexture().MakeDirty();
    315402        }
    316403       
    317404        if (CRenderer::IsInitialised())
    318405            g_Renderer.GetTimeManager().Update(deltaSimTime);
    319406    }
    320407
    321408    if (doInterpolate)
    322409    {
    323410        m_TurnManager->Interpolate(deltaSimTime, deltaRealTime);
    324411
  • source/ps/Game.h

     
    1111 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1212 * GNU General Public License for more details.
    1313 *
    1414 * You should have received a copy of the GNU General Public License
    1515 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
    1616 */
    1717
    1818#ifndef INCLUDED_GAME
    1919#define INCLUDED_GAME
    2020
    2121#include "ps/Errors.h"
    2222#include <vector>
    23 
     23#include <map>
    2424#include "scriptinterface/ScriptVal.h"
    2525
    2626class CWorld;
    2727class CSimulation2;
    2828class CGameView;
    2929class CNetTurnManager;
    3030class IReplayLogger;
    3131struct CColor;
    3232
    3333/**
    3434 * The container that holds the rules, resources and attributes of the game.
    3535 * The CGame object is responsible for creating a game that is defined by
     
    5656     **/
    5757    bool m_GameStarted;
    5858    /**
    5959     * Timescale multiplier for simulation rate.
    6060     **/
    6161    float m_SimRate;
    6262
    6363    int m_PlayerID;
    6464
    6565    CNetTurnManager* m_TurnManager;
    6666
    6767public:
    68     CGame(bool disableGraphics = false);
     68    CGame(bool disableGraphics = false, bool replayLog = true);
    6969    ~CGame();
    7070
    7171    /**
    7272     * the game is paused and no updates will be performed if true.
    7373     **/
    7474    bool m_Paused;
    7575
    7676    void StartGame(JS::MutableHandleValue attribs, const std::string& savedState);
    7777    PSRETURN ReallyStartGame();
    7878
     79    void StartReplay(const std::string& replayPath);
     80
    7981    /**
    8082     * Periodic heartbeat that controls the process. performs all per-frame updates.
    8183     * Simulation update is called and game status update is called.
    8284     *
    8385     * @param deltaRealTime Elapsed real time since last beat/frame, in seconds.
    8486     * @param doInterpolate Perform graphics interpolation if true.
    8587     * @return bool false if it can't keep up with the desired simulation rate
    8688     *  indicating that you might want to render less frequently.
    8789     */
    8890    bool Update(const double deltaRealTime, bool doInterpolate = true);
    8991
    9092    void Interpolate(float simFrameLength, float realFrameLength);
     
    162164    IReplayLogger& GetReplayLogger() const
    163165    {   return *m_ReplayLogger; }
    164166
    165167private:
    166168    void RegisterInit(const JS::HandleValue attribs, const std::string& savedState);
    167169    IReplayLogger* m_ReplayLogger;
    168170
    169171    std::vector<CColor> m_PlayerColors;
    170172
    171173    int LoadInitialState();
    172174    std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState
    173175    bool m_IsSavedGame; // true if loading a saved game; false for a new game
     176
     177    int LoadReplayData();
     178    std::string m_ReplayPath;
     179    bool m_IsReplay;
     180    std::istream* m_ReplayStream;
     181    u32 m_FinalReplayTurn;
    174182};
    175183
    176184extern CGame *g_Game;
    177185
    178186#endif
  • source/ps/GameSetup/GameSetup.cpp

     
    871871    // before launching Atlas)
    872872#if MUST_INIT_X11
    873873    int status = XInitThreads();
    874874    if (status == 0)
    875875        debug_printf("Error enabling thread-safety via XInitThreads\n");
    876876#endif
    877877
    878878    // Initialise the low-quality rand function
    879879    srand(time(NULL));  // NOTE: this rand should *not* be used for simulation!
    880880}
    881881
    882882bool Autostart(const CmdLineArgs& args);
     883bool VisualReplay(const CmdLineArgs& args);
    883884
    884885bool Init(const CmdLineArgs& args, int flags)
    885886{
    886887    h_mgr_init();
    887888
    888889    // Do this as soon as possible, because it chdirs
    889890    // and will mess up the error reporting if anything
    890891    // crashes before the working directory is set.
    891892    InitVfs(args, flags);
    892893
    893894    // This must come after VFS init, which sets the current directory
    894895    // (required for finding our output log files).
     
    10641065        g_Shadows = false;
    10651066    }
    10661067
    10671068    ogl_WarnIfError();
    10681069    InitRenderer();
    10691070
    10701071    InitInput();
    10711072
    10721073    ogl_WarnIfError();
    10731074
    10741075    try
    10751076    {
    1076         if (!Autostart(args))
     1077        if (!VisualReplay(args) && !Autostart(args))
    10771078        {
    10781079            const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
    10791080            // We only want to display the splash screen at startup
    10801081            shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetScriptInterface();
    10811082            JSContext* cx = scriptInterface->GetContext();
    10821083            JSAutoRequest rq(cx);
    10831084            JS::RootedValue data(cx);
    10841085            if (g_GUI)
    10851086            {
    10861087                scriptInterface->Eval("({})", &data);
    10871088                scriptInterface->SetProperty(data, "isStartup", true);
    10881089            }
     
    14641465
    14651466        LDR_NonprogressiveLoad();
    14661467
    14671468        PSRETURN ret = g_Game->ReallyStartGame();
    14681469        ENSURE(ret == PSRETURN_OK);
    14691470
    14701471        InitPs(true, L"page_session.xml", NULL, JS::UndefinedHandleValue);
    14711472    }
    14721473
    14731474    return true;
    14741475}
    14751476
     1477bool VisualReplay(const CmdLineArgs& args)
     1478{
     1479    CStr replayPath = args.Get("replay-visual");
     1480
     1481    if (replayPath.empty())
     1482         return false;
     1483
     1484    g_Game = new CGame(false, false);
     1485
     1486    g_Game->SetPlayerID(-1);
     1487    g_Game->StartReplay(replayPath);
     1488
     1489    // TODO: Non progressive load can fail - need a decent way to handle this
     1490    LDR_NonprogressiveLoad();
     1491
     1492    PSRETURN ret = g_Game->ReallyStartGame();
     1493    ENSURE(ret == PSRETURN_OK);
     1494
     1495    ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
     1496
     1497    InitPs(true, L"page_session.xml", &scriptInterface, JS::UndefinedHandleValue);
     1498    return true;
     1499}
     1500
    14761501void CancelLoad(const CStrW& message)
    14771502{
    14781503    shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
    14791504    JSContext* cx = pScriptInterface->GetContext();
    14801505    JSAutoRequest rq(cx);
    14811506   
    14821507    JS::RootedValue global(cx, pScriptInterface->GetGlobalObject());
    14831508    // Cancel loader
    14841509    LDR_Cancel();
    14851510
    14861511    // Call the cancelOnError GUI function, defined in ..gui/common/functions_utility_error.js
    14871512    // So all GUI pages that load games should include this script
  • source/ps/Replay.h

     
    4747     * Optional hash of simulation state (for sync checking).
    4848     */
    4949    virtual void Hash(const std::string& hash, bool quick) = 0;
    5050};
    5151
    5252/**
    5353 * Implementation of IReplayLogger that simply throws away all data.
    5454 */
    5555class CDummyReplayLogger : public IReplayLogger
    5656{
    5757public:
    5858    virtual void StartGame(JS::MutableHandleValue UNUSED(attribs)) { }
    59     virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), const std::vector<SimulationCommand>& UNUSED(commands)) { }
     59    virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), std::vector<SimulationCommand>& UNUSED(commands)) { }
    6060    virtual void Hash(const std::string& UNUSED(hash), bool UNUSED(quick)) { }
    6161};
    6262
    6363/**
    6464 * Implementation of IReplayLogger that saves data to a file in the logs directory.
    6565 */
    6666class CReplayLogger : public IReplayLogger
    6767{
    6868    NONCOPYABLE(CReplayLogger);
    6969public:
    7070    CReplayLogger(ScriptInterface& scriptInterface);
    7171    ~CReplayLogger();