Ticket #9: t0009_visualreplay_v1_r16653.patch

File t0009_visualreplay_v1_r16653.patch, 14.1 KB (added by elexis, 9 years ago)

No more out of sync. Should be ready to commit / ready for review. Other observer related features will be implemented in different tickets, like #3168.

  • source/network/NetTurnManager.cpp

     
    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
     487    // Initialize turn length
     488    if (turn == 0)
     489        m_TurnLength = m_ReplayTurnLengths[0];
     490}
    467491
     492void CNetReplayTurnManager::StoreFinalReplayTurn(u32 turn)
     493{
     494    m_FinalReplayTurn = turn;
     495}
     496void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
     497{
     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;
    468509
     510    // Compute hash
     511    std::string expectedHash;
     512    bool ok = m_Simulation2.ComputeStateHash(expectedHash, quick);
     513    expectedHash = Hexify(expectedHash);
     514    ENSURE(ok);
     515
     516    if (hash != expectedHash)
     517        debug_printf("Hash mismatch on turn %d (expected %s) (current %s)\n", turn, expectedHash.c_str(), hash.c_str());
     518}
     519
     520void CNetReplayTurnManager::DoTurn(u32 turn)
     521{
     522    m_TurnLength = m_ReplayTurnLengths[turn];
     523
     524    // Add commands
     525    std::map<int, std::vector<std::string> > playerCommands = m_ReplayCommands[turn];
     526    std::map<int, std::vector<std::string> >::iterator it;
     527    for (it = playerCommands.begin(); it != playerCommands.end(); ++it)
     528    {
     529        int player = it->first;
     530        for (size_t i = 0; i < it->second.size(); ++i)
     531        {
     532            JS::RootedValue data(m_Simulation2.GetScriptInterface().GetContext());
     533            m_Simulation2.GetScriptInterface().ParseJSON(it->second[i], &data);
     534            AddCommand(m_ClientId, player, data, m_CurrentTurn + 1);
     535        }
     536    }
     537}
    469538
    470539CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
    471540    m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP)
    472541{
    473542    // The first turn we will actually execute is number 2,
  • source/network/NetTurnManager.h

     
    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;
     
    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{
     
    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 *
  • source/ps/Game.cpp

     
    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());
     99
     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;
     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
    88149
     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()
     
    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)
     
    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{
     
    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
     
    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())
  • source/ps/Game.h

     
    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;
     
    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.
     
    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

     
    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
     
    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();
     
    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);
  • source/ps/Replay.h

     
    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.