Ticket #3433: 3433_replay_cache_v1.8.1.patch
File 3433_replay_cache_v1.8.1.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::ifstream cacheStream(cacheFileName.string8().c_str()); 76 // Read file into cacheStr 77 CStr cacheStr((std::istreambuf_iterator<char>(cacheStream)), std::istreambuf_iterator<char>()); 78 cacheStream.close(); 79 80 // Create empty JS object and parse the context of the cache into it 81 JS::RootedValue cachedReplays(cx); 82 if (!scriptInterface.ParseJSON(cacheStr, &cachedReplays)) 83 { 84 LOGERROR("The replay cache file is corrupted, it will be deleted"); 85 wunlink(cacheFileName); 86 return false; 87 } 88 89 cachedReplaysObject.set(&cachedReplays.toObject()); 90 if (!JS_IsArrayObject(cx, cachedReplaysObject)) 91 { 92 LOGERROR("The replay cache file is corrupted, it will be deleted"); 93 wunlink(cacheFileName); 94 return false; 95 } 96 97 return true; 98 } 99 100 void VisualReplay::StoreCacheFile(ScriptInterface& scriptInterface, JS::HandleObject replays) 101 { 102 TIMER(L"StoreCacheFile"); 103 JSContext* cx = scriptInterface.GetContext(); 104 JSAutoRequest rq(cx); 105 106 const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json"; 107 const OsPath tempCacheFileName = GetDirectoryName() / L"replayCache_temp.json"; 108 109 JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays)); 110 std::ofstream cacheStream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc); 111 cacheStream << scriptInterface.StringifyJSON(&replaysRooted); 112 cacheStream.close(); 113 114 wunlink(cacheFileName); 115 if (wrename(tempCacheFileName, cacheFileName)) 116 LOGERROR("Could not store the replay cache"); 117 } 118 64 119 /** 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) 120 * Load the replay cache and check if there are new/deleted ones 121 * If so, update their data. 122 */ 123 JS::HandleObject VisualReplay::ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize) 71 124 { 72 TIMER(L" GetReplays");125 TIMER(L"ReloadReplayCache"); 73 126 JSContext* cx = scriptInterface.GetContext(); 74 127 JSAutoRequest rq(cx); 128 // Maps the filename onto the index and size 129 std::map<CStr, std::pair<u32, u32>> fileList; 75 130 76 u32 i = 0; 131 JS::RootedObject cachedReplaysObject(cx); 132 133 134 if (ReadCacheFile(scriptInterface, &cachedReplaysObject)) 135 { 136 TIMER(L"ReloadReplayCache: create fileList"); 137 // Create list of files included in the cache 138 u32 cacheLength = 0; 139 JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength); 140 for (u32 j = 0; j < cacheLength; ++j) 141 { 142 JS::RootedValue replay(cx); 143 JS_GetElement(cx, cachedReplaysObject, j, &replay); 144 145 JS::RootedValue file(cx); 146 CStr fileName; 147 u32 fileSize; 148 scriptInterface.GetProperty(replay, "directory", fileName); 149 scriptInterface.GetProperty(replay, "fileSize", fileSize); 150 151 fileList.emplace(fileName, std::make_pair(j, fileSize)); 152 } 153 } 154 155 JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0)); 77 156 DirectoryNames directories; 78 JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));79 157 80 if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK) 81 for (OsPath& directory : directories) 158 if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK) 159 return replays; 160 161 bool newReplays = false; 162 std::vector<u32> copyFromOldCache; 163 // Specifies where the next replay data should go in replays 164 u32 i = 0; 165 { 166 TIMER(L"ReloadReplayCache: Load data"); 167 for (const OsPath& directory : directories) 82 168 { 83 169 if (SDL_QuitRequested()) 84 return JSVAL_NULL;170 return replays; 85 171 86 JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); 87 if (!replayData.isNull()) 172 bool isNew = true; 173 std::map<CStr, std::pair<u32, u32>>::iterator it = fileList.find(directory.string8()); 174 // directory is in fileList 175 if (it != fileList.end()) 176 { 177 if (compareFileSize) 178 { 179 CFileInfo fileInfo; 180 GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo); 181 if ((u32)fileInfo.Size() == it->second.second) 182 isNew = false; 183 } 184 else 185 isNew = false; 186 } 187 188 if (isNew) 189 { 190 JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); 191 if (replayData.isNull()) 192 { 193 CFileInfo fileInfo; 194 GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo); 195 scriptInterface.Eval("({})", &replayData); 196 scriptInterface.SetProperty(replayData, "directory", directory); 197 scriptInterface.SetProperty(replayData, "fileSize", (u32)fileInfo.Size()); 198 } 88 199 JS_SetElement(cx, replays, i++, replayData); 200 newReplays = true; 201 } 202 else 203 copyFromOldCache.push_back(it->second.first); 89 204 } 90 return JS::ObjectValue(*replays); 205 } 206 207 debug_printf("old not deleted replays: %d \n", copyFromOldCache.size()); 208 debug_printf("old deleted replays:: %d \n", fileList.size() - copyFromOldCache.size()); 209 debug_printf("new replays: %d \n", i); 210 211 if (!newReplays && fileList.empty()) 212 return replays; 213 // No replay was changed, so just return the cache 214 if (!newReplays && fileList.size() == copyFromOldCache.size()) 215 return cachedReplaysObject; 216 217 { 218 TIMER(L"ReloadReplayCache: copy from old cache"); 219 // Copy the replays from the old cache that are not deleted 220 if (!copyFromOldCache.empty()) 221 for (u32 j : copyFromOldCache) 222 { 223 JS::RootedValue replay(cx); 224 JS_GetElement(cx, cachedReplaysObject, j, &replay); 225 JS_SetElement(cx, replays, i++, replay); 226 } 227 } 228 StoreCacheFile(scriptInterface, replays); 229 return replays; 91 230 } 92 231 232 JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface, bool reload) 233 { 234 TIMER(L"GetReplays"); 235 JSContext* cx = scriptInterface.GetContext(); 236 JSAutoRequest rq(cx); 237 // Acquire replays 238 JS::RootedObject replays(cx, ReloadReplayCache(scriptInterface, reload)); 239 // Remove entries without data 240 JS::RootedObject replaysWithoutNullEntries(cx, JS_NewArrayObject(cx, 0)); 241 u32 replaysLength = 0; 242 u32 i = 0; 243 JS_GetArrayLength(cx, replays, &replaysLength); 244 for (u32 j = 0; j < replaysLength; ++j) 245 { 246 JS::RootedValue replay(cx); 247 JS_GetElement(cx, replays, j, &replay); 248 if (scriptInterface.HasProperty(replay, "attribs")) 249 JS_SetElement(cx, replaysWithoutNullEntries, i++, replay); 250 } 251 return JS::ObjectValue(*replaysWithoutNullEntries); 252 } 253 93 254 /** 94 255 * Move the cursor backwards until a newline was read or the beginning of the file was found. 95 256 * Either way the cursor points to the beginning of a newline. … … 173 334 return -1; 174 335 } 175 336 176 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)337 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory) 177 338 { 339 TIMER(L"LoadReplayData"); 178 340 // The directory argument must not be constant, otherwise concatenating will fail 179 341 const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt"; 180 342 … … 251 413 scriptInterface.Eval("({})", &replayData); 252 414 scriptInterface.SetProperty(replayData, "file", replayFile); 253 415 scriptInterface.SetProperty(replayData, "directory", directory); 416 scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize); 254 417 scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime)); 255 418 scriptInterface.SetProperty(replayData, "attribs", attribs); 256 419 scriptInterface.SetProperty(replayData, "duration", duration); … … 266 429 return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK; 267 430 } 268 431 269 270 432 JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) 271 433 { 272 434 // Create empty JS object … … 292 454 return attribs; 293 455 } 294 456 457 void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName) 458 { 459 TIMER(L"AddReplayToCache"); 460 JSContext* cx = scriptInterface.GetContext(); 461 JSAutoRequest rq(cx); 462 463 JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName))); 464 if (replayData.isNull()) 465 return; 466 467 JS::RootedObject cachedReplaysObject(cx); 468 if (!ReadCacheFile(scriptInterface, &cachedReplaysObject)) 469 cachedReplaysObject = JS_NewArrayObject(cx, 0); 470 471 u32 cacheLength = 0; 472 JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength); 473 JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData); 474 475 StoreCacheFile(scriptInterface, cachedReplaysObject); 476 } 477 295 478 void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface) 296 479 { 297 480 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