Ticket #9: visualreplay-WIP-r16530.patch
File visualreplay-WIP-r16530.patch, 20.2 KB (added by , 9 years ago) |
---|
-
source/network/NetTurnManager.cpp
455 455 bool ok = m_Simulation2.ComputeStateHash(hash); 456 456 ENSURE(ok); 457 457 } 458 458 m_Replay.Hash(hash); 459 459 #endif 460 460 } 461 461 462 462 void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg)) 463 463 { 464 464 debug_warn(L"This should never be called"); 465 465 } 466 466 467 CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) : 468 CNetLocalTurnManager(simulation, replay) 469 { 470 } 471 472 void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command) 473 { 474 m_ReplayCommands[turn][player].push_back(command); 475 } 476 477 void CNetReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick) 478 { 479 m_ReplayHash[turn] = std::make_pair(hash, quick); 480 } 481 482 void 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 } 467 487 488 void CNetReplayTurnManager::StoreFinalReplayTurn(u32 turn) 489 { 490 m_FinalReplayTurn = turn; 491 } 492 void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn) 493 { 494 // TODO: replay starts with turn 0, not 1 495 turn--; 468 496 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 526 void 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 } 469 547 470 548 CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) : 471 549 m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP) 472 550 { 473 551 // The first turn we will actually execute is number 2, 474 552 // so store dummy values into the saved lengths list 475 553 m_SavedTurnLengths.push_back(0); 476 554 m_SavedTurnLengths.push_back(0); 477 555 } 478 556 479 557 void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn) 480 558 { -
source/network/NetTurnManager.h
13 13 * 14 14 * You should have received a copy of the GNU General Public License 15 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 16 */ 17 17 18 18 #ifndef INCLUDED_NETTURNMANAGER 19 19 #define INCLUDED_NETTURNMANAGER 20 20 21 21 #include "simulation2/helpers/SimulationCommand.h" 22 22 23 23 #include <list> 24 24 #include <map> 25 25 #include <vector> 26 26 class CNetServerWorker; 27 27 class CNetClient; 28 28 class CSimulationMessage; 29 29 class CSimulation2; 30 30 class IReplayLogger; 31 31 32 32 /* 33 33 * This file deals with the logic of the network turn system. The basic idea is as in 34 34 * http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php?print=1 35 35 * 36 36 * Each player performs the simulation for turn N. 37 37 * User input is translated into commands scheduled for execution in turn N+2 which are … … 180 180 181 181 bool m_HasSyncError; 182 182 183 183 IReplayLogger& m_Replay; 184 184 185 185 private: 186 186 size_t m_TimeWarpNumTurns; // 0 if disabled 187 187 std::list<std::string> m_TimeWarpStates; 188 188 std::string m_QuickSaveState; // TODO: should implement a proper disk-based quicksave system 189 189 std::string m_QuickSaveMetadata; 190 190 }; 191 191 192 192 193 /** 193 194 * Implementation of CNetTurnManager for network clients. 194 195 */ 195 196 class CNetClientTurnManager : public CNetTurnManager 196 197 { 197 198 public: 198 199 CNetClientTurnManager(CSimulation2& simulation, CNetClient& client, int clientId, IReplayLogger& replay); 199 200 200 201 virtual void OnSimulationMessage(CSimulationMessage* msg); 201 202 202 203 virtual void PostCommand(JS::HandleValue data); 203 204 … … 224 225 225 226 virtual void OnSimulationMessage(CSimulationMessage* msg); 226 227 227 228 virtual void PostCommand(JS::HandleValue data); 228 229 229 230 protected: 230 231 virtual void NotifyFinishedOwnCommands(u32 turn); 231 232 232 233 virtual void NotifyFinishedUpdate(u32 turn); 233 234 }; 234 235 235 236 237 238 /** 239 * Implementation of CNetTurnManager for replay games. 240 */ 241 class CNetReplayTurnManager : public CNetLocalTurnManager 242 { 243 public: 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 255 protected: 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 }; 236 273 /** 237 274 * The server-side counterpart to CNetClientTurnManager. 238 275 * Records the turn state of each client, and sends turn advancement messages 239 276 * when all clients are ready. 240 277 * 241 278 * Thread-safety: 242 279 * - This is constructed and used by CNetServerWorker in the network server thread. 243 280 */ 244 281 class CNetServerTurnManager 245 282 { 246 283 NONCOPYABLE(CNetServerTurnManager); 247 284 public: -
source/ps/Game.cpp
54 54 extern bool g_GameRestarted; 55 55 extern GameLoopState* g_AtlasGameLoop; 56 56 57 57 /** 58 58 * Globally accessible pointer to the CGame object. 59 59 **/ 60 60 CGame *g_Game=NULL; 61 61 62 62 /** 63 63 * Constructor 64 64 * 65 65 **/ 66 CGame::CGame(bool disableGraphics ):66 CGame::CGame(bool disableGraphics, bool replayLog): 67 67 m_World(new CWorld(this)), 68 68 m_Simulation2(new CSimulation2(&m_World->GetUnitManager(), g_ScriptRuntime, m_World->GetTerrain())), 69 69 m_GameView(disableGraphics ? NULL : new CGameView(this)), 70 70 m_GameStarted(false), 71 71 m_Paused(false), 72 72 m_SimRate(1.0f), 73 73 m_PlayerID(-1), 74 m_IsSavedGame(false) 74 m_IsSavedGame(false), 75 m_IsReplay(false), 76 m_ReplayStream(NULL) 75 77 { 76 m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());77 78 // 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(); 78 83 79 84 // Need to set the CObjectManager references after various objects have 80 85 // been initialised, so do it here rather than via the initialisers above. 81 86 if (m_GameView) 82 87 m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager()); 83 88 84 89 m_TurnManager = new CNetLocalTurnManager(*m_Simulation2, GetReplayLogger()); // this will get replaced if we're a net server/client 85 90 86 91 m_Simulation2->LoadDefaultScripts(); 87 92 } 93 int CGame::LoadReplayData() 94 { 95 ENSURE(m_IsReplay); 96 ENSURE(!m_ReplayPath.empty()); 97 98 CNetReplayTurnManager* replayTurnMgr = static_cast<CNetReplayTurnManager*>(GetTurnManager()); 88 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 + 1; 138 replayTurnMgr->StoreFinalReplayTurn(currentTurn); 139 return 0; 140 } 141 void 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 } 89 168 /** 90 169 * Destructor 91 170 * 92 171 **/ 93 172 CGame::~CGame() 94 173 { 95 174 // Again, the in-game call tree is going to be different to the main menu one. 96 175 if (CProfileManager::IsInitialised()) 97 176 g_Profiler.StructuralReset(); 98 177 99 178 delete m_TurnManager; 100 179 delete m_GameView; 101 180 delete m_Simulation2; 102 181 delete m_World; 103 182 delete m_ReplayLogger; 183 delete m_ReplayStream; 104 184 } 105 185 106 186 void CGame::SetTurnManager(CNetTurnManager* turnManager) 107 187 { 108 188 if (m_TurnManager) 109 189 delete m_TurnManager; 110 190 111 191 m_TurnManager = turnManager; 112 192 113 193 if (m_TurnManager) 114 194 m_TurnManager->SetPlayerID(m_PlayerID); 115 195 } … … 167 247 JS::RootedValue settings(cx); 168 248 scriptInterface.GetProperty(attribs, "map", mapFile); 169 249 scriptInterface.GetProperty(attribs, "settings", &settings); 170 250 171 251 m_World->RegisterInit(mapFile, scriptInterface.GetJSRuntime(), settings, m_PlayerID); 172 252 } 173 253 if (m_GameView) 174 254 RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80); 175 255 176 256 if (m_IsSavedGame) 177 257 RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000); 178 258 259 if (m_IsReplay) 260 RegMemFun(this, &CGame::LoadReplayData, L"Loading replay data", 1000); 261 179 262 LDR_EndRegistering(); 180 263 } 181 264 182 265 int CGame::LoadInitialState() 183 266 { 184 267 ENSURE(m_IsSavedGame); 185 268 ENSURE(!m_InitialSavedState.empty()); 186 269 187 270 std::string state; 188 271 m_InitialSavedState.swap(state); // deletes the original to save a bit of memory 189 272 190 273 std::stringstream stream(state); … … 263 346 return m_PlayerID; 264 347 } 265 348 266 349 void CGame::SetPlayerID(int playerID) 267 350 { 268 351 m_PlayerID = playerID; 269 352 if (m_TurnManager) 270 353 m_TurnManager->SetPlayerID(m_PlayerID); 271 354 } 272 355 273 356 void CGame::StartGame(JS::MutableHandleValue attribs, const std::string& savedState) 274 357 { 358 if (m_ReplayLogger != false) 275 359 m_ReplayLogger->StartGame(attribs); 360 276 361 RegisterInit(attribs, savedState); 277 362 } 278 363 279 364 // TODO: doInterpolate is optional because Atlas interpolates explicitly, 280 365 // so that it has more control over the update rate. The game might want to 281 366 // do the same, and then doInterpolate should be redundant and removed. 282 367 283 368 bool CGame::Update(const double deltaRealTime, bool doInterpolate) 284 369 { 285 370 if (m_Paused) 286 371 return true; 287 372 … … 301 386 // At the normal sim rate, we currently want to render at least one 302 387 // frame per simulation turn, so let maxTurns be 1. But for fast-forward 303 388 // sim rates we want to allow more, so it's not bounded by framerate, 304 389 // so just use the sim rate itself as the number of turns per frame. 305 390 size_t maxTurns = (size_t)m_SimRate; 306 391 307 392 if (m_TurnManager->Update(deltaSimTime, maxTurns)) 308 393 { 309 394 { 310 395 PROFILE3("gui sim update"); 311 396 g_GUI->SendEventToAll("SimulationUpdate"); 312 397 } 398 if (m_IsReplay && m_TurnManager->GetCurrentTurn() == m_FinalReplayTurn) 399 g_GUI->SendEventToAll("ReplayFinished"); 313 400 314 401 GetView()->GetLOSTexture().MakeDirty(); 315 402 } 316 403 317 404 if (CRenderer::IsInitialised()) 318 405 g_Renderer.GetTimeManager().Update(deltaSimTime); 319 406 } 320 407 321 408 if (doInterpolate) 322 409 { 323 410 m_TurnManager->Interpolate(deltaSimTime, deltaRealTime); 324 411 -
source/ps/Game.h
11 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 12 * GNU General Public License for more details. 13 13 * 14 14 * You should have received a copy of the GNU General Public License 15 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 16 */ 17 17 18 18 #ifndef INCLUDED_GAME 19 19 #define INCLUDED_GAME 20 20 21 21 #include "ps/Errors.h" 22 22 #include <vector> 23 23 #include <map> 24 24 #include "scriptinterface/ScriptVal.h" 25 25 26 26 class CWorld; 27 27 class CSimulation2; 28 28 class CGameView; 29 29 class CNetTurnManager; 30 30 class IReplayLogger; 31 31 struct CColor; 32 32 33 33 /** 34 34 * The container that holds the rules, resources and attributes of the game. 35 35 * The CGame object is responsible for creating a game that is defined by … … 56 56 **/ 57 57 bool m_GameStarted; 58 58 /** 59 59 * Timescale multiplier for simulation rate. 60 60 **/ 61 61 float m_SimRate; 62 62 63 63 int m_PlayerID; 64 64 65 65 CNetTurnManager* m_TurnManager; 66 66 67 67 public: 68 CGame(bool disableGraphics = false );68 CGame(bool disableGraphics = false, bool replayLog = true); 69 69 ~CGame(); 70 70 71 71 /** 72 72 * the game is paused and no updates will be performed if true. 73 73 **/ 74 74 bool m_Paused; 75 75 76 76 void StartGame(JS::MutableHandleValue attribs, const std::string& savedState); 77 77 PSRETURN ReallyStartGame(); 78 78 79 void StartReplay(const std::string& replayPath); 80 79 81 /** 80 82 * Periodic heartbeat that controls the process. performs all per-frame updates. 81 83 * Simulation update is called and game status update is called. 82 84 * 83 85 * @param deltaRealTime Elapsed real time since last beat/frame, in seconds. 84 86 * @param doInterpolate Perform graphics interpolation if true. 85 87 * @return bool false if it can't keep up with the desired simulation rate 86 88 * indicating that you might want to render less frequently. 87 89 */ 88 90 bool Update(const double deltaRealTime, bool doInterpolate = true); 89 91 90 92 void Interpolate(float simFrameLength, float realFrameLength); … … 162 164 IReplayLogger& GetReplayLogger() const 163 165 { return *m_ReplayLogger; } 164 166 165 167 private: 166 168 void RegisterInit(const JS::HandleValue attribs, const std::string& savedState); 167 169 IReplayLogger* m_ReplayLogger; 168 170 169 171 std::vector<CColor> m_PlayerColors; 170 172 171 173 int LoadInitialState(); 172 174 std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState 173 175 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; 174 182 }; 175 183 176 184 extern CGame *g_Game; 177 185 178 186 #endif -
source/ps/GameSetup/GameSetup.cpp
871 871 // before launching Atlas) 872 872 #if MUST_INIT_X11 873 873 int status = XInitThreads(); 874 874 if (status == 0) 875 875 debug_printf("Error enabling thread-safety via XInitThreads\n"); 876 876 #endif 877 877 878 878 // Initialise the low-quality rand function 879 879 srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! 880 880 } 881 881 882 882 bool Autostart(const CmdLineArgs& args); 883 bool VisualReplay(const CmdLineArgs& args); 883 884 884 885 bool Init(const CmdLineArgs& args, int flags) 885 886 { 886 887 h_mgr_init(); 887 888 888 889 // Do this as soon as possible, because it chdirs 889 890 // and will mess up the error reporting if anything 890 891 // crashes before the working directory is set. 891 892 InitVfs(args, flags); 892 893 893 894 // This must come after VFS init, which sets the current directory 894 895 // (required for finding our output log files). … … 1064 1065 g_Shadows = false; 1065 1066 } 1066 1067 1067 1068 ogl_WarnIfError(); 1068 1069 InitRenderer(); 1069 1070 1070 1071 InitInput(); 1071 1072 1072 1073 ogl_WarnIfError(); 1073 1074 1074 1075 try 1075 1076 { 1076 if (! Autostart(args))1077 if (!VisualReplay(args) && !Autostart(args)) 1077 1078 { 1078 1079 const bool setup_gui = ((flags & INIT_NO_GUI) == 0); 1079 1080 // We only want to display the splash screen at startup 1080 1081 shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetScriptInterface(); 1081 1082 JSContext* cx = scriptInterface->GetContext(); 1082 1083 JSAutoRequest rq(cx); 1083 1084 JS::RootedValue data(cx); 1084 1085 if (g_GUI) 1085 1086 { 1086 1087 scriptInterface->Eval("({})", &data); 1087 1088 scriptInterface->SetProperty(data, "isStartup", true); 1088 1089 } … … 1464 1465 1465 1466 LDR_NonprogressiveLoad(); 1466 1467 1467 1468 PSRETURN ret = g_Game->ReallyStartGame(); 1468 1469 ENSURE(ret == PSRETURN_OK); 1469 1470 1470 1471 InitPs(true, L"page_session.xml", NULL, JS::UndefinedHandleValue); 1471 1472 } 1472 1473 1473 1474 return true; 1474 1475 } 1475 1476 1477 bool 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 1476 1501 void CancelLoad(const CStrW& message) 1477 1502 { 1478 1503 shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface(); 1479 1504 JSContext* cx = pScriptInterface->GetContext(); 1480 1505 JSAutoRequest rq(cx); 1481 1506 1482 1507 JS::RootedValue global(cx, pScriptInterface->GetGlobalObject()); 1483 1508 // Cancel loader 1484 1509 LDR_Cancel(); 1485 1510 1486 1511 // Call the cancelOnError GUI function, defined in ..gui/common/functions_utility_error.js 1487 1512 // So all GUI pages that load games should include this script -
source/ps/Replay.h
47 47 * Optional hash of simulation state (for sync checking). 48 48 */ 49 49 virtual void Hash(const std::string& hash, bool quick) = 0; 50 50 }; 51 51 52 52 /** 53 53 * Implementation of IReplayLogger that simply throws away all data. 54 54 */ 55 55 class CDummyReplayLogger : public IReplayLogger 56 56 { 57 57 public: 58 58 virtual void StartGame(JS::MutableHandleValue UNUSED(attribs)) { } 59 virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), conststd::vector<SimulationCommand>& UNUSED(commands)) { }59 virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), std::vector<SimulationCommand>& UNUSED(commands)) { } 60 60 virtual void Hash(const std::string& UNUSED(hash), bool UNUSED(quick)) { } 61 61 }; 62 62 63 63 /** 64 64 * Implementation of IReplayLogger that saves data to a file in the logs directory. 65 65 */ 66 66 class CReplayLogger : public IReplayLogger 67 67 { 68 68 NONCOPYABLE(CReplayLogger); 69 69 public: 70 70 CReplayLogger(ScriptInterface& scriptInterface); 71 71 ~CReplayLogger();