Ticket #3261: t3261_savegame_checkpoints_wip_v0.01.patch
File t3261_savegame_checkpoints_wip_v0.01.patch, 14.4 KB (added by , 8 years ago) |
---|
-
binaries/data/config/default.cfg
rotate.y.smoothness = 0.3 411 411 near = 2.0 ; Near plane distance 412 412 far = 4096.0 ; Far plane distance 413 413 fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide 414 414 height.smoothness = 0.5 415 415 height.min = 16 416 417 [replay.save.session] 418 enabled = false ; Whether to generate savepoints in running games to skip / rewind replays (caution: requires significant disk space and can cause lag spikes) 419 interval = 300 ; save the gamestate every 5 minutes 420 421 [replay.save.replay] 422 enabled = true ; Whether to generate savepoints while replaying 423 interval = 300 ; save the gamestate every 5 minutes -
binaries/data/mods/public/gui/session/session.js
var g_CivData = {}; 82 82 /** 83 83 * For restoring selection, order and filters when returning to the replay menu 84 84 */ 85 85 var g_ReplaySelectionData; 86 86 87 /** 88 * Simulation time in milliseconds. 89 */ 90 var g_ReplayLastSave = 0; 91 87 92 var g_PlayerAssignments = { 88 93 "local": { 89 94 "name": singleplayerName(), 90 95 "player": 1 91 96 } … … function onTick() 582 587 583 588 checkPlayerState(); 584 589 585 590 handleNetMessages(); 586 591 592 saveReplayStates(); 593 587 594 updateCursorAndTooltip(); 588 595 589 596 if (g_Selection.dirty) 590 597 { 591 598 g_Selection.dirty = false; … … function reportGame() 1284 1291 reportObject.lootCollected = playerStatistics.lootCollected; 1285 1292 reportObject.tradeIncome = playerStatistics.tradeIncome; 1286 1293 1287 1294 Engine.SendGameReport(reportObject); 1288 1295 } 1296 1297 /** 1298 * Write a savegame in frequent intervals for seeking replays. 1299 */ 1300 function saveReplayStates() 1301 { 1302 let type = g_IsReplay ? "replay" : "session"; 1303 1304 if (!Engine.ConfigDB_GetValue("user", "replay.save." + type + ".enabled")) 1305 return; 1306 1307 let now = GetSimState().timeElapsed; 1308 1309 // Randomize the first time the game is being saved, 1310 // to avoid clients serializing simultaneously. 1311 // -> Likely better to do it parallel than in series 1312 //if (!g_IsReplay && !g_ReplayLastSave) 1313 // g_ReplayLastSave = Math.random() * 10; 1314 1315 let duration = Engine.ConfigDB_GetValue("user", "replay.save." + type + ".interval"); 1316 duration = Math.max(duration, 1) * 1000; 1317 1318 if (now - g_ReplayLastSave < duration) 1319 return; 1320 1321 // Don't save if we already got a checkpoint. 1322 // Do this just before saving to be compatible for concurrent replays 1323 if (false && g_IsReplay) 1324 { 1325 let states = Engine.GetReplayStates(); 1326 // states is an array of { "metadata": "", "filename": "" } 1327 // TODO: loop over states and dont save if there is a recent one 1328 } 1329 1330 // TODO: access the JS Timer component from c++ directly 1331 if (Engine.SaveReplayState(now)) 1332 g_ReplayLastSave = now; 1333 } -
source/ps/SavedGame.cpp
Status SavedGames::Save(const CStrW& nam 72 72 73 73 OsPath tempSaveFileRealPath; 74 74 WARN_RETURN_STATUS_IF_ERR(g_VFS->GetDirectoryRealPath("cache/", tempSaveFileRealPath)); 75 75 tempSaveFileRealPath = tempSaveFileRealPath / "temp.0adsave"; 76 76 77 WARN_RETURN_STATUS_IF_ERR(WriteSavegameTemp(description, simulation, guiMetadataClone, tempSaveFileRealPath)); 78 79 WriteBuffer buffer; 80 CFileInfo tempSaveFile; 81 WARN_RETURN_STATUS_IF_ERR(GetFileInfo(tempSaveFileRealPath, &tempSaveFile)); 82 buffer.Reserve(tempSaveFile.Size()); 83 WARN_RETURN_STATUS_IF_ERR(io::Load(tempSaveFileRealPath, buffer.Data().get(), buffer.Size())); 84 WARN_RETURN_STATUS_IF_ERR(g_VFS->CreateFile(filename, buffer.Data(), buffer.Size())); 85 86 OsPath realPath; 87 WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath)); 88 LOGMESSAGERENDER(g_L10n.Translate("Saved game to '%s'"), realPath.string8()); 89 debug_printf("Saved game to '%s'\n", realPath.string8().c_str()); 90 91 return INFO::OK; 92 } 93 94 Status SavedGames::WriteSavegameTemp(const CStrW& description, CSimulation2& simulation, shared_ptr<ScriptInterface::StructuredClone> guiMetadataClone, OsPath& targetFile) 95 { 96 JSContext* cx = simulation.GetScriptInterface().GetContext(); 97 JSAutoRequest rq(cx); 98 77 99 time_t now = time(NULL); 78 100 79 101 // Construct the serialized state to be saved 80 102 81 103 std::stringstream simStateStream; … … Status SavedGames::Save(const CStrW& nam 111 133 simulation.GetScriptInterface().SetProperty(metadata, "description", description); 112 134 113 135 std::string metadataString = simulation.GetScriptInterface().StringifyJSON(&metadata, true); 114 136 115 137 // Write the saved game as zip file containing the various components 116 PIArchiveWriter archiveWriter = CreateArchiveWriter_Zip(t empSaveFileRealPath, false);138 PIArchiveWriter archiveWriter = CreateArchiveWriter_Zip(targetFile, false); 117 139 if (!archiveWriter) 118 140 WARN_RETURN(ERR::FAIL); 119 141 120 142 WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)metadataString.c_str(), metadataString.length(), now, "metadata.json")); 121 143 WARN_RETURN_STATUS_IF_ERR(archiveWriter->AddMemory((const u8*)simStateStream.str().c_str(), simStateStream.str().length(), now, "simulation.dat")); 122 144 archiveWriter.reset(); // close the file 123 145 124 WriteBuffer buffer;125 CFileInfo tempSaveFile;126 WARN_RETURN_STATUS_IF_ERR(GetFileInfo(tempSaveFileRealPath, &tempSaveFile));127 buffer.Reserve(tempSaveFile.Size());128 WARN_RETURN_STATUS_IF_ERR(io::Load(tempSaveFileRealPath, buffer.Data().get(), buffer.Size()));129 WARN_RETURN_STATUS_IF_ERR(g_VFS->CreateFile(filename, buffer.Data(), buffer.Size()));130 131 OsPath realPath;132 WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath));133 LOGMESSAGERENDER(g_L10n.Translate("Saved game to '%s'"), realPath.string8());134 debug_printf("Saved game to '%s'\n", realPath.string8().c_str());135 136 146 return INFO::OK; 137 147 } 138 148 139 149 /** 140 150 * Helper class for retrieving data from saved game archives -
source/ps/SavedGame.h
namespace SavedGames 46 46 * @param gui if not NULL, store some UI-related data with the saved game 47 47 * @return INFO::OK if successfully saved, else an error Status 48 48 */ 49 49 Status Save(const CStrW& name, const CStrW& description, CSimulation2& simulation, shared_ptr<ScriptInterface::StructuredClone> guiMetadataClone); 50 50 51 52 /** 53 * Creates a savegame file, also used by replays. 54 */ 55 Status WriteSavegameTemp(const CStrW& description, CSimulation2& simulation, shared_ptr<ScriptInterface::StructuredClone> guiMetadataClone, OsPath& output); 56 51 57 /** 52 58 * Create new saved game archive with given prefix and simulation data 53 59 * 54 60 * @param prefix Create new numbered file starting with this prefix 55 61 * @param description A user-given description of the save -
source/ps/VisualReplay.cpp
18 18 #include "precompiled.h" 19 19 20 20 #include "VisualReplay.h" 21 21 #include "graphics/GameView.h" 22 22 #include "gui/GUIManager.h" 23 #include "i18n/L10n.h" 23 24 #include "lib/allocators/shared_ptr.h" 24 25 #include "lib/external_libraries/libsdl.h" 25 26 #include "lib/utf8.h" 26 27 #include "network/NetClient.h" 27 28 #include "network/NetServer.h" … … 30 31 #include "ps/Game.h" 31 32 #include "ps/GameSetup/Paths.h" 32 33 #include "ps/Mod.h" 33 34 #include "ps/Pyrogenesis.h" 34 35 #include "ps/Replay.h" 36 #include "ps/SavedGame.h" 35 37 #include "ps/Util.h" 36 38 #include "scriptinterface/ScriptInterface.h" 37 39 38 40 /** 39 41 * Filter too short replays (value in seconds). … … JS::Value VisualReplay::GetReplayMetadat 330 332 SAFE_DELETE(stream); 331 333 pCxPrivate->pScriptInterface->ParseJSON(line, &metadata); 332 334 333 335 return metadata; 334 336 } 337 338 bool VisualReplay::SaveReplayState(ScriptInterface::CxPrivate* pCxPrivate, int simTime, JS::HandleValue GUIMetadata) 339 { 340 shared_ptr<ScriptInterface::StructuredClone> guiMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata); 341 342 OsPath output = g_Game->GetReplayLogger().GetDirectory() / L"savegames"; 343 CreateDirectories(output, 0700); 344 345 output = output / (std::to_string(simTime) + CStr(".0adsave")); 346 347 if (SavedGames::WriteSavegameTemp(L"Replay", *g_Game->GetSimulation2(), guiMetadataClone, output) != INFO::OK) 348 return false; 349 350 // TODO: something like IComponent* component = componentManager->QueryInterface(SYSTEM_ENTITY, IID_Timer); 351 352 LOGMESSAGERENDER(g_L10n.Translate("Saved replay checkpoint to '%s'"), output.string8()); 353 debug_printf("Saved replay checkpoint to %s\n", output.string8().c_str()); 354 355 return true; 356 } 357 358 JS::Value VisualReplay::GetReplayStates(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directory) 359 { 360 TIMER(L"GetReplayStates"); 361 362 JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); 363 JSAutoRequest rq(cx); 364 365 u32 i = 0; 366 DirectoryNames filenames; 367 JS::RootedObject replayStates(cx, JS_NewArrayObject(cx, 0)); 368 369 if (GetDirectoryEntries(directory, NULL, &filenames) == INFO::OK) 370 for (OsPath& filepath : filenames) 371 { 372 CStr filename = filepath.Filename().string8(); 373 374 if (filename.substr(filename.size() - 8) == ".0adsave") 375 continue; 376 377 int time = std::atoi(filename.substr(0, filename.size() - 8).c_str()); 378 379 JS::RootedValue replayStateInfo(cx); 380 pCxPrivate->pScriptInterface->Eval("({})", &replayStateInfo); 381 pCxPrivate->pScriptInterface->SetProperty(replayStateInfo, "file", filename); 382 pCxPrivate->pScriptInterface->SetProperty(replayStateInfo, "time", time); 383 JS_SetElement(cx, replayStates, i++, replayStateInfo); 384 } 385 386 return JS::ObjectValue(*replayStates); 387 } -
source/ps/VisualReplay.h
1 /* Copyright (C) 201 5Wildfire Games.1 /* Copyright (C) 2016 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 5 * it under the terms of the GNU General Public License as published by 6 6 * the Free Software Foundation, either version 2 of the License, or … … JS::Value GetReplayMetadata(ScriptInterf 81 81 /** 82 82 * Saves the metadata from the session to metadata.json 83 83 */ 84 84 void SaveReplayMetadata(const CStrW& data); 85 85 86 /** 87 * Saves the serialized gamestate to the replay directory to allow skipping and rewinding replays. 88 */ 89 bool SaveReplayState(ScriptInterface::CxPrivate* pCxPrivate, int simTime, JS::HandleValue GUIMetadata); 90 91 /** 92 * Given a replay directory, returns an array of integers identifying the gametime of every savestate. 93 */ 94 JS::Value GetReplayStates(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directory); 95 86 96 } 87 97 88 98 #endif -
source/ps/scripting/JSInterface_VisualReplay.cpp
1 /* Copyright (C) 201 5Wildfire Games.1 /* Copyright (C) 2016 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 5 * it under the terms of the GNU General Public License as published by 6 6 * the Free Software Foundation, either version 2 of the License, or … … JS::Value JSI_VisualReplay::GetReplayMet 53 53 void JSI_VisualReplay::SaveReplayMetadata(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const CStrW& data) 54 54 { 55 55 VisualReplay::SaveReplayMetadata(data); 56 56 } 57 57 58 // TODO: support u64 59 bool JSI_VisualReplay::SaveReplayState(ScriptInterface::CxPrivate* pCxPrivate, int simTime, JS::HandleValue GUIMetadata) 60 { 61 return VisualReplay::SaveReplayState(pCxPrivate, simTime, GUIMetadata); 62 } 63 64 JS::Value JSI_VisualReplay::GetReplayStates(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directory) 65 { 66 return VisualReplay::GetReplayStates(pCxPrivate, directory); 67 } 68 58 69 void JSI_VisualReplay::RegisterScriptFunctions(ScriptInterface& scriptInterface) 59 70 { 60 71 scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays"); 61 72 scriptInterface.RegisterFunction<bool, CStrW, &DeleteReplay>("DeleteReplay"); 62 73 scriptInterface.RegisterFunction<void, CStrW, &StartVisualReplay>("StartVisualReplay"); 63 74 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayAttributes>("GetReplayAttributes"); 64 75 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayMetadata>("GetReplayMetadata"); 65 76 scriptInterface.RegisterFunction<bool, CStrW, &HasReplayMetadata>("HasReplayMetadata"); 66 77 scriptInterface.RegisterFunction<void, CStrW, &SaveReplayMetadata>("SaveReplayMetadata"); 78 scriptInterface.RegisterFunction<bool, int, JS::HandleValue, &SaveReplayState>("SaveReplayState"); 79 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayStates>("GetReplayStates"); 67 80 } -
source/ps/scripting/JSInterface_VisualReplay.h
1 /* Copyright (C) 201 5Wildfire Games.1 /* Copyright (C) 2016 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 5 * it under the terms of the GNU General Public License as published by 6 6 * the Free Software Foundation, either version 2 of the License, or … … namespace JSI_VisualReplay 28 28 JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate); 29 29 JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName); 30 30 bool HasReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName); 31 31 JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName); 32 32 void SaveReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& data); 33 bool SaveReplayState(ScriptInterface::CxPrivate* pCxPrivate, int, JS::HandleValue GUIMetadata); 34 JS::Value GetReplayStates(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directory); 33 35 void RegisterScriptFunctions(ScriptInterface& scriptInterface); 34 36 } 35 37 36 38 #endif