Ticket #3433: 3433_replay_cache_v1.8.1patch

File 3433_replay_cache_v1.8.1patch, 16.7 KB (added by Imarok, 7 years ago)

see above comment

Line 
1Index: binaries/data/mods/public/gui/replaymenu/replay_actions.js
2===================================================================
3--- binaries/data/mods/public/gui/replaymenu/replay_actions.js (revision 19041)
4+++ binaries/data/mods/public/gui/replaymenu/replay_actions.js (working copy)
5@@ -127,6 +127,12 @@
6 });
7 }
8
9+function reloadCache()
10+{
11+ let selected = Engine.GetGUIObjectByName("replaySelection").selected;
12+ loadReplays(selected > 0 ? createReplaySelectionData(g_ReplaysFiltered[selected].directory) : "", true);
13+}
14+
15 /**
16 * Callback.
17 */
18Index: binaries/data/mods/public/gui/replaymenu/replay_menu.js
19===================================================================
20--- binaries/data/mods/public/gui/replaymenu/replay_menu.js (revision 19041)
21+++ binaries/data/mods/public/gui/replaymenu/replay_menu.js (working copy)
22@@ -59,7 +59,7 @@
23 return;
24 }
25
26- loadReplays(data && data.replaySelectionData);
27+ loadReplays(data && data.replaySelectionData, false);
28
29 if (!g_Replays)
30 {
31@@ -76,9 +76,9 @@
32 * Check timestamp and compatibility and extract g_Playernames, g_MapNames, g_VictoryConditions.
33 * Restore selected filters and item.
34 */
35-function loadReplays(replaySelectionData)
36+function loadReplays(replaySelectionData, reload)
37 {
38- g_Replays = Engine.GetReplays();
39+ g_Replays = Engine.GetReplays(reload);
40
41 if (!g_Replays)
42 return;
43Index: binaries/data/mods/public/gui/replaymenu/replay_menu.xml
44===================================================================
45--- binaries/data/mods/public/gui/replaymenu/replay_menu.xml (revision 19041)
46+++ binaries/data/mods/public/gui/replaymenu/replay_menu.xml (working copy)
47@@ -250,6 +250,12 @@
48 <translatableAttribute id="caption">Delete</translatableAttribute>
49 <action on="Press">deleteReplayButtonPressed();</action>
50 </object>
51+
52+ <!-- Reload Cache Button -->
53+ <object type="button" style="StoneButton" size="40%+25 0 57%+25 100%">
54+ <translatableAttribute id="caption">Reload Cache</translatableAttribute>
55+ <action on="Press">reloadCache();</action>
56+ </object>
57
58 <!-- Summary Button -->
59 <object name="summaryButton" type="button" style="StoneButton" size="65%-50 0 82%-50 100%">
60Index: binaries/data/mods/public/gui/session/session.js
61===================================================================
62--- binaries/data/mods/public/gui/session/session.js (revision 19041)
63+++ binaries/data/mods/public/gui/session/session.js (working copy)
64@@ -583,6 +583,11 @@
65
66 Engine.EndGame();
67
68+ // After the replay file was closed in EndGame
69+ // Done here to keep EndGame small
70+ if (!g_IsReplay)
71+ Engine.AddReplayToCache(replayDirectory);
72+
73 if (g_IsController && Engine.HasXmppClient())
74 Engine.SendUnregisterGame();
75
76Index: source/ps/scripting/JSInterface_VisualReplay.cpp
77===================================================================
78--- source/ps/scripting/JSInterface_VisualReplay.cpp (revision 19041)
79+++ source/ps/scripting/JSInterface_VisualReplay.cpp (working copy)
80@@ -33,9 +33,9 @@
81 return VisualReplay::DeleteReplay(replayFile);
82 }
83
84-JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate)
85+JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate, bool reload)
86 {
87- return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface));
88+ return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface), reload);
89 }
90
91 JS::Value JSI_VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
92@@ -53,12 +53,18 @@
93 return VisualReplay::GetReplayMetadata(pCxPrivate, directoryName);
94 }
95
96+void JSI_VisualReplay::AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
97+{
98+ VisualReplay::AddReplayToCache(*(pCxPrivate->pScriptInterface), directoryName);
99+}
100+
101 void JSI_VisualReplay::RegisterScriptFunctions(ScriptInterface& scriptInterface)
102 {
103- scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays");
104+ scriptInterface.RegisterFunction<JS::Value, bool, &GetReplays>("GetReplays");
105 scriptInterface.RegisterFunction<bool, CStrW, &DeleteReplay>("DeleteReplay");
106 scriptInterface.RegisterFunction<void, CStrW, &StartVisualReplay>("StartVisualReplay");
107 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayAttributes>("GetReplayAttributes");
108 scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayMetadata>("GetReplayMetadata");
109 scriptInterface.RegisterFunction<bool, CStrW, &HasReplayMetadata>("HasReplayMetadata");
110+ scriptInterface.RegisterFunction<void, CStrW, &AddReplayToCache>("AddReplayToCache");
111 }
112Index: source/ps/scripting/JSInterface_VisualReplay.h
113===================================================================
114--- source/ps/scripting/JSInterface_VisualReplay.h (revision 19041)
115+++ source/ps/scripting/JSInterface_VisualReplay.h (working copy)
116@@ -25,10 +25,11 @@
117 {
118 void StartVisualReplay(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directory);
119 bool DeleteReplay(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& replayFile);
120- JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate);
121+ JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate, bool reload);
122 JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
123 bool HasReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
124 JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
125+ void AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
126 void RegisterScriptFunctions(ScriptInterface& scriptInterface);
127 }
128
129Index: source/ps/VisualReplay.cpp
130===================================================================
131--- source/ps/VisualReplay.cpp (revision 19041)
132+++ source/ps/VisualReplay.cpp (working copy)
133@@ -61,35 +61,196 @@
134 g_Game->StartVisualReplay(replayFile.string8());
135 }
136
137+bool VisualReplay::ReadCacheFile(ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject)
138+{
139+ TIMER(L"ReadCacheFile");
140+ JSContext* cx = scriptInterface.GetContext();
141+ JSAutoRequest rq(cx);
142+
143+ const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
144+ if (!FileExists(cacheFileName))
145+ return false;
146+
147+ // Open cache file
148+ std::ifstream cacheStream(cacheFileName.string8().c_str());
149+ // Read file into cacheStr
150+ CStr cacheStr((std::istreambuf_iterator<char>(cacheStream)), std::istreambuf_iterator<char>());
151+ cacheStream.close();
152+
153+ // Create empty JS object and parse the context of the cache into it
154+ JS::RootedValue cachedReplays(cx);
155+ if (!scriptInterface.ParseJSON(cacheStr, &cachedReplays))
156+ {
157+ LOGERROR("The replay cache file is corrupted, it will be deleted");
158+ wunlink(cacheFileName);
159+ return false;
160+ }
161+
162+ cachedReplaysObject.set(&cachedReplays.toObject());
163+ if (!JS_IsArrayObject(cx, cachedReplaysObject))
164+ {
165+ LOGERROR("The replay cache file is corrupted, it will be deleted");
166+ wunlink(cacheFileName);
167+ return false;
168+ }
169+
170+ return true;
171+}
172+
173+void VisualReplay::StoreCacheFile(ScriptInterface& scriptInterface, JS::HandleObject replays)
174+{
175+ TIMER(L"StoreCacheFile");
176+ JSContext* cx = scriptInterface.GetContext();
177+ JSAutoRequest rq(cx);
178+
179+ const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
180+ const OsPath tempCacheFileName = GetDirectoryName() / L"replayCache_temp.json";
181+
182+ JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays));
183+ std::ofstream cacheStream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
184+ cacheStream << scriptInterface.StringifyJSON(&replaysRooted);
185+ cacheStream.close();
186+
187+ wunlink(cacheFileName);
188+ if (wrename(tempCacheFileName, cacheFileName))
189+ LOGERROR("Could not store the replay cache");
190+}
191+
192 /**
193- * Load all replays found in the directory.
194- *
195- * Since files are spread across the harddisk,
196- * loading hundreds of them can consume a lot of time.
197- */
198-JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface)
199+* Load the replay cache and check if there are new/deleted ones
200+* If so, update their data.
201+*/
202+JS::HandleObject VisualReplay::ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize)
203 {
204- TIMER(L"GetReplays");
205+ TIMER(L"ReloadReplayCache");
206 JSContext* cx = scriptInterface.GetContext();
207 JSAutoRequest rq(cx);
208+ // Maps the filename onto the index and size
209+ std::map<CStr, std::pair<u32, u32>> fileList;
210
211- u32 i = 0;
212+ JS::RootedObject cachedReplaysObject(cx);
213+
214+
215+ if (ReadCacheFile(scriptInterface, &cachedReplaysObject))
216+ {
217+ TIMER(L"ReloadReplayCache: create fileList");
218+ // Create list of files included in the cache
219+ u32 cacheLength = 0;
220+ JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
221+ for (u32 j = 0; j < cacheLength; ++j)
222+ {
223+ JS::RootedValue replay(cx);
224+ JS_GetElement(cx, cachedReplaysObject, j, &replay);
225+
226+ JS::RootedValue file(cx);
227+ CStr fileName;
228+ u32 fileSize;
229+ scriptInterface.GetProperty(replay, "directory", fileName);
230+ scriptInterface.GetProperty(replay, "fileSize", fileSize);
231+
232+ fileList.emplace(fileName, std::make_pair(j, fileSize));
233+ }
234+ }
235+
236+ JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
237 DirectoryNames directories;
238- JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
239
240- if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK)
241- for (OsPath& directory : directories)
242+ if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK)
243+ return replays;
244+
245+ bool newReplays = false;
246+ std::vector<u32> copyFromOldCache;
247+ // Specifies where the next replay data should go in replays
248+ u32 i = 0;
249+ {
250+ TIMER(L"ReloadReplayCache: Load data");
251+ for (const OsPath& directory : directories)
252 {
253 if (SDL_QuitRequested())
254- return JSVAL_NULL;
255+ return replays;
256
257- JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
258- if (!replayData.isNull())
259+ bool isNew = true;
260+ std::map<CStr, std::pair<u32, u32>>::iterator it = fileList.find(directory.string8());
261+ // directory is in fileList
262+ if (it != fileList.end())
263+ {
264+ if (compareFileSize)
265+ {
266+ CFileInfo fileInfo;
267+ GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
268+ if ((u32)fileInfo.Size() == it->second.second)
269+ isNew = false;
270+ }
271+ else
272+ isNew = false;
273+ }
274+
275+ if (isNew)
276+ {
277+ JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
278+ if (replayData.isNull())
279+ {
280+ CFileInfo fileInfo;
281+ GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
282+ scriptInterface.Eval("({})", &replayData);
283+ scriptInterface.SetProperty(replayData, "directory", directory);
284+ scriptInterface.SetProperty(replayData, "fileSize", (u32)fileInfo.Size());
285+ }
286 JS_SetElement(cx, replays, i++, replayData);
287+ newReplays = true;
288+ }
289+ else
290+ copyFromOldCache.push_back(it->second.first);
291 }
292- return JS::ObjectValue(*replays);
293+ }
294+
295+ debug_printf("old not deleted replays: %d \n", copyFromOldCache.size());
296+ debug_printf("old deleted replays:: %d \n", fileList.size() - copyFromOldCache.size());
297+ debug_printf("new replays: %d \n", i);
298+
299+ if (!newReplays && fileList.empty())
300+ return replays;
301+ // No replay was changed, so just return the cache
302+ if (!newReplays && fileList.size() == copyFromOldCache.size())
303+ return cachedReplaysObject;
304+
305+ {
306+ TIMER(L"ReloadReplayCache: copy from old cache");
307+ // Copy the replays from the old cache that are not deleted
308+ if (!copyFromOldCache.empty())
309+ for (u32 j : copyFromOldCache)
310+ {
311+ JS::RootedValue replay(cx);
312+ JS_GetElement(cx, cachedReplaysObject, j, &replay);
313+ JS_SetElement(cx, replays, i++, replay);
314+ }
315+ }
316+ StoreCacheFile(scriptInterface, replays);
317+ return replays;
318 }
319
320+JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface, bool reload)
321+{
322+ TIMER(L"GetReplays");
323+ JSContext* cx = scriptInterface.GetContext();
324+ JSAutoRequest rq(cx);
325+ // Acquire replays
326+ JS::RootedObject replays(cx, ReloadReplayCache(scriptInterface, reload));
327+ // Remove entries without data
328+ JS::RootedObject replaysWithoutNullEntries(cx, JS_NewArrayObject(cx, 0));
329+ u32 replaysLength = 0;
330+ u32 i = 0;
331+ JS_GetArrayLength(cx, replays, &replaysLength);
332+ for (u32 j = 0; j < replaysLength; ++j)
333+ {
334+ JS::RootedValue replay(cx);
335+ JS_GetElement(cx, replays, j, &replay);
336+ if (scriptInterface.HasProperty(replay, "attribs"))
337+ JS_SetElement(cx, replaysWithoutNullEntries, i++, replay);
338+ }
339+ return JS::ObjectValue(*replaysWithoutNullEntries);
340+}
341+
342 /**
343 * Move the cursor backwards until a newline was read or the beginning of the file was found.
344 * Either way the cursor points to the beginning of a newline.
345@@ -173,8 +334,9 @@
346 return -1;
347 }
348
349-JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)
350+JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory)
351 {
352+ TIMER(L"LoadReplayData");
353 // The directory argument must not be constant, otherwise concatenating will fail
354 const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt";
355
356@@ -251,6 +413,7 @@
357 scriptInterface.Eval("({})", &replayData);
358 scriptInterface.SetProperty(replayData, "file", replayFile);
359 scriptInterface.SetProperty(replayData, "directory", directory);
360+ scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize);
361 scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime));
362 scriptInterface.SetProperty(replayData, "attribs", attribs);
363 scriptInterface.SetProperty(replayData, "duration", duration);
364@@ -266,7 +429,6 @@
365 return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK;
366 }
367
368-
369 JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
370 {
371 // Create empty JS object
372@@ -292,6 +454,27 @@
373 return attribs;
374 }
375
376+void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName)
377+{
378+ TIMER(L"AddReplayToCache");
379+ JSContext* cx = scriptInterface.GetContext();
380+ JSAutoRequest rq(cx);
381+
382+ JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName)));
383+ if (replayData.isNull())
384+ return;
385+
386+ JS::RootedObject cachedReplaysObject(cx);
387+ if (!ReadCacheFile(scriptInterface, &cachedReplaysObject))
388+ cachedReplaysObject = JS_NewArrayObject(cx, 0);
389+
390+ u32 cacheLength = 0;
391+ JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
392+ JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData);
393+
394+ StoreCacheFile(scriptInterface, cachedReplaysObject);
395+}
396+
397 void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface)
398 {
399 JSContext* cx = scriptInterface->GetContext();
400Index: source/ps/VisualReplay.h
401===================================================================
402--- source/ps/VisualReplay.h (revision 19041)
403+++ source/ps/VisualReplay.h (working copy)
404@@ -42,18 +42,44 @@
405 void StartVisualReplay(const CStrW& directory);
406
407 /**
408+ * Reads the replay Cache file and parses it into a jsObject
409+ *
410+ * @param scriptInterface the ScriptInterface in which to create the return data.
411+ * @param cachedReplaysObject the cached replays
412+ * @return true on succes; false otherwise
413+ */
414+bool ReadCacheFile(ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject);
415+
416+/**
417+ * Stores the replay list in the replay cache file
418+ *
419+ * @param scriptInterface the ScriptInterface in which to create the return data.
420+ * @param replays teh replay list to store
421+ */
422+void StoreCacheFile(ScriptInterface& scriptInterface, JS::HandleObject replays);
423+
424+/**
425+ * Reloads the replay cache
426+ *
427+ * @param scriptInterface the ScriptInterface in which to create the return data.
428+ * @param compareFileSize compare the directory name and the FileSize of the replays and the cache
429+ * @return cache entries
430+ */
431+JS::HandleObject ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize = false);
432+
433+/**
434 * Get a list of replays to display in the GUI.
435 *
436 * @param scriptInterface the ScriptInterface in which to create the return data.
437 * @return array of objects containing replay data
438 */
439-JS::Value GetReplays(ScriptInterface& scriptInterface);
440+JS::Value GetReplays(ScriptInterface& scriptInterface, bool reload);
441
442 /**
443 * Parses a commands.txt file and extracts metadata.
444 * Works similarly to CGame::LoadReplayData().
445 */
446-JS::Value LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory);
447+JS::Value LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory);
448
449 /**
450 * Permanently deletes the visual replay (including the parent directory)
451@@ -83,6 +109,10 @@
452 */
453 void SaveReplayMetadata(ScriptInterface* scriptInterface);
454
455+/**
456+* Adds a replay to the replayCache
457+*/
458+void AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName);
459 }
460
461 #endif