Ticket #3433: 3433_replay_cache_v1.8.patch
File 3433_replay_cache_v1.8.patch, 16.7 KB (added by , 7 years ago) |
---|
-
binaries/data/mods/public/gui/replaymenu/replay_actions.js
127 127 }); 128 128 } 129 129 130 function reloadCache() 131 { 132 let selected = Engine.GetGUIObjectByName("replaySelection").selected; 133 loadReplays(selected > 0 ? createReplaySelectionData(g_ReplaysFiltered[selected].directory) : "", true); 134 } 135 130 136 /** 131 137 * Callback. 132 138 */ -
binaries/data/mods/public/gui/replaymenu/replay_menu.js
59 59 return; 60 60 } 61 61 62 loadReplays(data && data.replaySelectionData );62 loadReplays(data && data.replaySelectionData, false); 63 63 64 64 if (!g_Replays) 65 65 { … … 76 76 * Check timestamp and compatibility and extract g_Playernames, g_MapNames, g_VictoryConditions. 77 77 * Restore selected filters and item. 78 78 */ 79 function loadReplays(replaySelectionData )79 function loadReplays(replaySelectionData, reload) 80 80 { 81 g_Replays = Engine.GetReplays( );81 g_Replays = Engine.GetReplays(reload); 82 82 83 83 if (!g_Replays) 84 84 return; -
binaries/data/mods/public/gui/replaymenu/replay_menu.xml
250 250 <translatableAttribute id="caption">Delete</translatableAttribute> 251 251 <action on="Press">deleteReplayButtonPressed();</action> 252 252 </object> 253 254 <!-- Reload Cache Button --> 255 <object type="button" style="StoneButton" size="40%+25 0 57%+25 100%"> 256 <translatableAttribute id="caption">Reload Cache</translatableAttribute> 257 <action on="Press">reloadCache();</action> 258 </object> 253 259 254 260 <!-- Summary Button --> 255 261 <object name="summaryButton" type="button" style="StoneButton" size="65%-50 0 82%-50 100%"> -
binaries/data/mods/public/gui/session/session.js
583 583 584 584 Engine.EndGame(); 585 585 586 // After the replay file was closed in EndGame 587 // Done here to keep EndGame small 588 if (!g_IsReplay) 589 Engine.AddReplayToCache(replayDirectory); 590 586 591 if (g_IsController && Engine.HasXmppClient()) 587 592 Engine.SendUnregisterGame(); 588 593 -
source/ps/scripting/JSInterface_VisualReplay.cpp
33 33 return VisualReplay::DeleteReplay(replayFile); 34 34 } 35 35 36 JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate )36 JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate, bool reload) 37 37 { 38 return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface) );38 return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface), reload); 39 39 } 40 40 41 41 JS::Value JSI_VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) … … 53 53 return VisualReplay::GetReplayMetadata(pCxPrivate, directoryName); 54 54 } 55 55 56 void JSI_VisualReplay::AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) 57 { 58 VisualReplay::AddReplayToCache(*(pCxPrivate->pScriptInterface), directoryName); 59 } 60 56 61 void JSI_VisualReplay::RegisterScriptFunctions(ScriptInterface& scriptInterface) 57 62 { 58 scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays");63 scriptInterface.RegisterFunction<JS::Value, bool, &GetReplays>("GetReplays"); 59 64 scriptInterface.RegisterFunction<bool, CStrW, &DeleteReplay>("DeleteReplay"); 60 65 scriptInterface.RegisterFunction<void, CStrW, &StartVisualReplay>("StartVisualReplay"); 61 66 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayAttributes>("GetReplayAttributes"); 62 67 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayMetadata>("GetReplayMetadata"); 63 68 scriptInterface.RegisterFunction<bool, CStrW, &HasReplayMetadata>("HasReplayMetadata"); 69 scriptInterface.RegisterFunction<void, CStrW, &AddReplayToCache>("AddReplayToCache"); 64 70 } -
source/ps/scripting/JSInterface_VisualReplay.h
25 25 { 26 26 void StartVisualReplay(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directory); 27 27 bool DeleteReplay(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& replayFile); 28 JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate );28 JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate, bool reload); 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 void AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName); 32 33 void RegisterScriptFunctions(ScriptInterface& scriptInterface); 33 34 } 34 35 -
source/ps/VisualReplay.cpp
61 61 g_Game->StartVisualReplay(replayFile.string8()); 62 62 } 63 63 64 bool VisualReplay::ReadCacheFile(ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject) 65 { 66 TIMER(L"ReadCacheFile"); 67 JSContext* cx = scriptInterface.GetContext(); 68 JSAutoRequest rq(cx); 69 70 const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json"; 71 if (!FileExists(cacheFileName)) 72 return false; 73 74 // Open cache file 75 std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str()); 76 77 // Read file into chacheStr 78 CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>()); 79 SAFE_DELETE(cacheStream); 80 81 // Create empty JS object and parse the context of the cache into it 82 JS::RootedValue cachedReplays(cx); 83 if (!scriptInterface.ParseJSON(cacheStr, &cachedReplays)) 84 { 85 LOGERROR("The replay cache file is corrupted, it will be deleted"); 86 wunlink(cacheFileName); 87 return false; 88 } 89 90 cachedReplaysObject.set(&cachedReplays.toObject()); 91 if (!JS_IsArrayObject(cx, cachedReplaysObject)) 92 { 93 LOGERROR("The replay cache file is corrupted, it will be deleted"); 94 wunlink(cacheFileName); 95 return false; 96 } 97 98 return true; 99 } 100 101 void VisualReplay::StoreCacheFile(ScriptInterface& scriptInterface, JS::HandleObject replays) 102 { 103 TIMER(L"StoreCacheFile"); 104 JSContext* cx = scriptInterface.GetContext(); 105 JSAutoRequest rq(cx); 106 107 const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json"; 108 const OsPath tempCacheFileName = GetDirectoryName() / L"replayCache_temp.json"; 109 110 JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays)); 111 std::ofstream cacheStream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc); 112 cacheStream << scriptInterface.StringifyJSON(&replaysRooted); 113 cacheStream.close(); 114 115 wunlink(cacheFileName); 116 if (wrename(tempCacheFileName, cacheFileName)) 117 LOGERROR("Could not store the replay cache"); 118 } 119 64 120 /** 65 * Load all replays found in the directory. 66 * 67 * Since files are spread across the harddisk, 68 * loading hundreds of them can consume a lot of time. 69 */ 70 JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface) 121 * Load the replay cache and check if there are new/deleted ones 122 * If so, update their data. 123 */ 124 JS::HandleObject VisualReplay::ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize) 71 125 { 72 TIMER(L" GetReplays");126 TIMER(L"ReloadReplayCache"); 73 127 JSContext* cx = scriptInterface.GetContext(); 74 128 JSAutoRequest rq(cx); 129 // Maps the filename onto the index and size 130 std::map<CStr, std::pair<u32, u32>> fileList; 75 131 76 u32 i = 0; 132 JS::RootedObject cachedReplaysObject(cx); 133 134 135 if (ReadCacheFile(scriptInterface, &cachedReplaysObject)) 136 { 137 TIMER(L"ReloadReplayCache: create fileList"); 138 // Create list of files included in the cache 139 u32 cacheLength = 0; 140 JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength); 141 for (u32 j = 0; j < cacheLength; ++j) 142 { 143 JS::RootedValue replay(cx); 144 JS_GetElement(cx, cachedReplaysObject, j, &replay); 145 146 JS::RootedValue file(cx); 147 CStr fileName; 148 u32 fileSize; 149 scriptInterface.GetProperty(replay, "directory", fileName); 150 scriptInterface.GetProperty(replay, "fileSize", fileSize); 151 152 fileList.emplace(fileName, std::make_pair(j, fileSize)); 153 } 154 } 155 156 JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0)); 77 157 DirectoryNames directories; 78 JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));79 158 80 if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK) 81 for (OsPath& directory : directories) 159 if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK) 160 return replays; 161 162 bool newReplays = false; 163 std::vector<u32> copyFromOldCache; 164 // Specifies where the next replay data should go in replays 165 u32 i = 0; 166 { 167 TIMER(L"ReloadReplayCache: Load data"); 168 for (const OsPath& directory : directories) 82 169 { 83 170 if (SDL_QuitRequested()) 84 return JSVAL_NULL;171 break; 85 172 86 JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); 87 if (!replayData.isNull()) 173 bool isNew = true; 174 std::map<CStr, std::pair<u32, u32>>::iterator it = fileList.find(directory.string8()); 175 // directory is in fileList 176 if (it != fileList.end()) 177 { 178 if (compareFileSize) 179 { 180 CFileInfo fileInfo; 181 GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo); 182 if ((u32)fileInfo.Size() == it->second.second) 183 isNew = false; 184 } 185 else 186 isNew = false; 187 } 188 189 if (isNew) 190 { 191 JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); 192 if (replayData.isNull()) 193 { 194 CFileInfo fileInfo; 195 GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo); 196 scriptInterface.Eval("({})", &replayData); 197 scriptInterface.SetProperty(replayData, "directory", directory); 198 scriptInterface.SetProperty(replayData, "fileSize", (u32)fileInfo.Size()); 199 } 88 200 JS_SetElement(cx, replays, i++, replayData); 201 newReplays = true; 202 } 203 else 204 copyFromOldCache.push_back(it->second.first); 89 205 } 90 return JS::ObjectValue(*replays); 206 } 207 208 debug_printf("old not deleted replays: %d \n", copyFromOldCache.size()); 209 debug_printf("old deleted replays:: %d \n", fileList.size() - copyFromOldCache.size()); 210 debug_printf("new replays: %d \n", i); 211 212 if (!newReplays && fileList.empty()) 213 return replays; 214 // No replay was changed, so just return the cache 215 if (!newReplays && fileList.size() == copyFromOldCache.size()) 216 return cachedReplaysObject; 217 218 { 219 TIMER(L"ReloadReplayCache: copy from old cache"); 220 // Copy the replays from the old cache that are not deleted 221 if (!copyFromOldCache.empty()) 222 for (u32 j : copyFromOldCache) 223 { 224 JS::RootedValue replay(cx); 225 JS_GetElement(cx, cachedReplaysObject, j, &replay); 226 JS_SetElement(cx, replays, i++, replay); 227 } 228 } 229 StoreCacheFile(scriptInterface, replays); 230 return replays; 91 231 } 92 232 233 JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface, bool reload) 234 { 235 TIMER(L"GetReplays"); 236 JSContext* cx = scriptInterface.GetContext(); 237 JSAutoRequest rq(cx); 238 // Acquire replays 239 JS::RootedObject replays(cx, ReloadReplayCache(scriptInterface, reload)); 240 // Remove entries without data 241 JS::RootedObject replaysWithoutNullEntries(cx, JS_NewArrayObject(cx, 0)); 242 u32 replaysLength = 0; 243 u32 i = 0; 244 JS_GetArrayLength(cx, replays, &replaysLength); 245 for (u32 j = 0; j < replaysLength; ++j) 246 { 247 JS::RootedValue replay(cx); 248 JS_GetElement(cx, replays, j, &replay); 249 if (scriptInterface.HasProperty(replay, "attribs")) 250 JS_SetElement(cx, replaysWithoutNullEntries, i++, replay); 251 } 252 return JS::ObjectValue(*replaysWithoutNullEntries); 253 } 254 93 255 /** 94 256 * Move the cursor backwards until a newline was read or the beginning of the file was found. 95 257 * Either way the cursor points to the beginning of a newline. … … 173 335 return -1; 174 336 } 175 337 176 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)338 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory) 177 339 { 340 TIMER(L"LoadReplayData"); 178 341 // The directory argument must not be constant, otherwise concatenating will fail 179 342 const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt"; 180 343 … … 251 414 scriptInterface.Eval("({})", &replayData); 252 415 scriptInterface.SetProperty(replayData, "file", replayFile); 253 416 scriptInterface.SetProperty(replayData, "directory", directory); 417 scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize); 254 418 scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime)); 255 419 scriptInterface.SetProperty(replayData, "attribs", attribs); 256 420 scriptInterface.SetProperty(replayData, "duration", duration); … … 266 430 return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK; 267 431 } 268 432 269 270 433 JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) 271 434 { 272 435 // Create empty JS object … … 292 455 return attribs; 293 456 } 294 457 458 void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName) 459 { 460 TIMER(L"AddReplayToCache"); 461 JSContext* cx = scriptInterface.GetContext(); 462 JSAutoRequest rq(cx); 463 464 JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName))); 465 if (replayData.isNull()) 466 return; 467 468 JS::RootedObject cachedReplaysObject(cx); 469 if (!ReadCacheFile(scriptInterface, &cachedReplaysObject)) 470 cachedReplaysObject = JS_NewArrayObject(cx, 0); 471 472 u32 cacheLength = 0; 473 JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength); 474 JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData); 475 476 StoreCacheFile(scriptInterface, cachedReplaysObject); 477 } 478 295 479 void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface) 296 480 { 297 481 JSContext* cx = scriptInterface->GetContext(); -
source/ps/VisualReplay.h
42 42 void StartVisualReplay(const CStrW& directory); 43 43 44 44 /** 45 * Reads the replay Cache file and parses it into a jsObject 46 * 47 * @param scriptInterface the ScriptInterface in which to create the return data. 48 * @param cachedReplaysObject the cached replays 49 * @return true on succes; false otherwise 50 */ 51 bool ReadCacheFile(ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject); 52 53 /** 54 * Stores the replay list in the replay cache file 55 * 56 * @param scriptInterface the ScriptInterface in which to create the return data. 57 * @param replays teh replay list to store 58 */ 59 void StoreCacheFile(ScriptInterface& scriptInterface, JS::HandleObject replays); 60 61 /** 62 * Reloads the replay cache 63 * 64 * @param scriptInterface the ScriptInterface in which to create the return data. 65 * @param compareFileSize compare the directory name and the FileSize of the replays and the cache 66 * @return cache entries 67 */ 68 JS::HandleObject ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize = false); 69 70 /** 45 71 * Get a list of replays to display in the GUI. 46 72 * 47 73 * @param scriptInterface the ScriptInterface in which to create the return data. 48 74 * @return array of objects containing replay data 49 75 */ 50 JS::Value GetReplays(ScriptInterface& scriptInterface );76 JS::Value GetReplays(ScriptInterface& scriptInterface, bool reload); 51 77 52 78 /** 53 79 * Parses a commands.txt file and extracts metadata. 54 80 * Works similarly to CGame::LoadReplayData(). 55 81 */ 56 JS::Value LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory);82 JS::Value LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory); 57 83 58 84 /** 59 85 * Permanently deletes the visual replay (including the parent directory) … … 83 109 */ 84 110 void SaveReplayMetadata(ScriptInterface* scriptInterface); 85 111 112 /** 113 * Adds a replay to the replayCache 114 */ 115 void AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName); 86 116 } 87 117 88 118 #endif