Ticket #3433: 3433_replay_cache_v1.7.patch

File 3433_replay_cache_v1.7.patch, 15.9 KB (added by Imarok, 8 years ago)

Readd the reload button. Only compare fileSize when reloading the cache, because that is very time-consuming. Include replays without data into the cache.

  • binaries/data/mods/public/gui/replaymenu/replay_actions.js

     
    127127    });
    128128}
    129129
     130function reloadCache()
     131{
     132    let selected = Engine.GetGUIObjectByName("replaySelection").selected;
     133    loadReplays(selected > 0 ? createReplaySelectionData(g_ReplaysFiltered[selected].directory) : "", true);
     134}
     135
    130136/**
    131137 * Callback.
    132138 */
  • binaries/data/mods/public/gui/replaymenu/replay_menu.js

     
    5959        return;
    6060    }
    6161
    62     loadReplays(data && data.replaySelectionData);
     62    loadReplays(data && data.replaySelectionData, false);
    6363
    6464    if (!g_Replays)
    6565    {
     
    7676 * Check timestamp and compatibility and extract g_Playernames, g_MapNames, g_VictoryConditions.
    7777 * Restore selected filters and item.
    7878 */
    79 function loadReplays(replaySelectionData)
     79function loadReplays(replaySelectionData, reload)
    8080{
    81     g_Replays = Engine.GetReplays();
     81    g_Replays = Engine.GetReplays(reload);
    8282
    8383    if (!g_Replays)
    8484        return;
  • binaries/data/mods/public/gui/replaymenu/replay_menu.xml

     
    250250                <translatableAttribute id="caption">Delete</translatableAttribute>
    251251                <action on="Press">deleteReplayButtonPressed();</action>
    252252            </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>
    253259
    254260            <!-- Summary Button -->
    255261            <object name="summaryButton" type="button" style="StoneButton" size="65%-50 0 82%-50 100%">
  • binaries/data/mods/public/gui/session/session.js

     
    559559
    560560    Engine.EndGame();
    561561
     562    // After the replay file was closed in EndGame
     563    // Done here to keep EndGame small
     564    if (!g_IsReplay)
     565        Engine.AddReplayToCache(replayDirectory);
     566
    562567    if (g_IsController && Engine.HasXmppClient())
    563568        Engine.SendUnregisterGame();
    564569
  • source/ps/scripting/JSInterface_VisualReplay.cpp

     
    3333    return VisualReplay::DeleteReplay(replayFile);
    3434}
    3535
    36 JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate)
     36JS::Value JSI_VisualReplay::GetReplays(ScriptInterface::CxPrivate* pCxPrivate, bool reload)
    3737{
    38     return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface));
     38    return VisualReplay::GetReplays(*(pCxPrivate->pScriptInterface), reload);
    3939}
    4040
    4141JS::Value JSI_VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
     
    5353    return VisualReplay::GetReplayMetadata(pCxPrivate, directoryName);
    5454}
    5555
     56void JSI_VisualReplay::AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
     57{
     58    VisualReplay::AddReplayToCache(*(pCxPrivate->pScriptInterface), directoryName);
     59}
     60
    5661void JSI_VisualReplay::RegisterScriptFunctions(ScriptInterface& scriptInterface)
    5762{
    58     scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays");
     63    scriptInterface.RegisterFunction<JS::Value, bool, &GetReplays>("GetReplays");
    5964    scriptInterface.RegisterFunction<bool, CStrW, &DeleteReplay>("DeleteReplay");
    6065    scriptInterface.RegisterFunction<void, CStrW, &StartVisualReplay>("StartVisualReplay");
    6166    scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayAttributes>("GetReplayAttributes");
    6267    scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayMetadata>("GetReplayMetadata");
    6368    scriptInterface.RegisterFunction<bool, CStrW, &HasReplayMetadata>("HasReplayMetadata");
     69    scriptInterface.RegisterFunction<void, CStrW, &AddReplayToCache>("AddReplayToCache");
    6470}
  • source/ps/scripting/JSInterface_VisualReplay.h

     
    2525{
    2626    void StartVisualReplay(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directory);
    2727    bool DeleteReplay(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& replayFile);
    28     JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate);
     28    JS::Value GetReplays(ScriptInterface::CxPrivate* pCxPrivate, bool reload);
    2929    JS::Value GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
    3030    bool HasReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
    3131    JS::Value GetReplayMetadata(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
     32    void AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName);
    3233    void RegisterScriptFunctions(ScriptInterface& scriptInterface);
    3334}
    3435
  • source/ps/VisualReplay.cpp

     
    6262}
    6363
    6464/**
    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)
     65* Load the replay cache and check if there are new/deleted ones
     66* If so, update their data.
     67*/
     68JS::HandleObject VisualReplay::ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize)
    7169{
    72     TIMER(L"GetReplays");
     70    TIMER(L"ReloadReplayCache");
    7371    JSContext* cx = scriptInterface.GetContext();
    7472    JSAutoRequest rq(cx);
    7573
    76     u32 i = 0;
     74    // Maps the filename onto the index and size
     75    std::map<CStr, std::pair<u32, u32>> fileList;
     76
     77    const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
     78    const OsPath tempCacheFileName = GetDirectoryName() / L"replayCache_temp.json";
     79    JS::RootedObject cachedReplaysObject(cx);
     80
     81    if (FileExists(cacheFileName))
     82    {
     83        TIMER(L"ReloadReplayCache: openCacheFile");
     84        // Open cache file
     85        std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str());
     86
     87        // Read file into chacheStr
     88        CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>());
     89
     90        // Create empty JS object and parse the context of the cache into it
     91        JS::RootedValue cachedReplays(cx);
     92        scriptInterface.ParseJSON(cacheStr, &cachedReplays);
     93        SAFE_DELETE(cacheStream);
     94
     95        cachedReplaysObject = &cachedReplays.toObject();
     96
     97        // Create list of files included in the cache
     98        u32 cacheLength = 0;
     99        JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     100        for (u32 j = 0; j < cacheLength; ++j)
     101        {
     102            JS::RootedValue replay(cx);
     103            JS_GetElement(cx, cachedReplaysObject, j, &replay);
     104
     105            JS::RootedValue file(cx);
     106            CStr fileName;
     107            u32 fileSize;
     108            scriptInterface.GetProperty(replay, "directory", fileName);
     109            scriptInterface.GetProperty(replay, "fileSize", fileSize);
     110
     111            fileList.emplace(fileName, std::make_pair(j, fileSize));
     112        }
     113    }
     114
     115    JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
    77116    DirectoryNames directories;
    78     JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
    79117
    80     if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK)
    81         for (OsPath& directory : directories)
     118    if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK)
     119        return replays;
     120
     121    bool newReplays = false;
     122    std::vector<u32> copyFromOldCache;
     123    // Specifies where the next replay data should go in replays
     124    u32 i = 0;
     125    {
     126        TIMER(L"ReloadReplayCache: Load data");
     127        for (const OsPath& directory : directories)
    82128        {
    83129            if (SDL_QuitRequested())
    84                 return JSVAL_NULL;
     130                break;
    85131
    86             JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
    87             if (!replayData.isNull())
     132            bool isNew = true;
     133            std::map<CStr, std::pair<u32, u32>>::iterator it = fileList.find(directory.string8());
     134            // directory is in fileList
     135            if (it != fileList.end())
     136            {
     137                if (compareFileSize)
     138                {
     139                    CFileInfo fileInfo;
     140                    GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
     141                    if ((u32)fileInfo.Size() == it->second.second)
     142                        isNew = false;
     143                }
     144                else
     145                    isNew = false;
     146            }
     147
     148            if (isNew)
     149            {
     150                JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
     151                if (replayData.isNull())
     152                {
     153                    CFileInfo fileInfo;
     154                    GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
     155                    scriptInterface.Eval("({})", &replayData);
     156                    scriptInterface.SetProperty(replayData, "directory", directory);
     157                    scriptInterface.SetProperty(replayData, "fileSize", (u32)fileInfo.Size());
     158                }
    88159                JS_SetElement(cx, replays, i++, replayData);
     160                newReplays = true;
     161            }
     162            else
     163                copyFromOldCache.push_back(it->second.first);
    89164        }
    90     return JS::ObjectValue(*replays);
     165    }
     166
     167    debug_printf("old not deleted replays: %d \n", copyFromOldCache.size());
     168    debug_printf("old deleted replays:: %d \n", fileList.size() - copyFromOldCache.size());
     169    debug_printf("new replays: %d \n", i);
     170
     171    if (!newReplays && fileList.empty())
     172        return replays;
     173    // No replay was changed, so just return the cache
     174    if (!newReplays && fileList.size() == copyFromOldCache.size())
     175        return cachedReplaysObject;
     176
     177    {
     178        TIMER(L"ReloadReplayCache: copy from old cache");
     179        // Copy the replays from the old cache that are not deleted
     180        if (!copyFromOldCache.empty())
     181            for (u32 j : copyFromOldCache)
     182            {
     183                JS::RootedValue replay(cx);
     184                JS_GetElement(cx, cachedReplaysObject, j, &replay);
     185                JS_SetElement(cx, replays, i++, replay);
     186            }
     187    }
     188
     189    {
     190        TIMER(L"ReloadReplayCache: save new cache");
     191        JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays));
     192        std::ofstream stream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     193        stream << scriptInterface.StringifyJSON(&replaysRooted);
     194        stream.close();
     195
     196        wunlink(cacheFileName);
     197        if (wrename(tempCacheFileName, cacheFileName))
     198            LOGERROR("Could not store the replay cache");
     199    }
     200    return replays;
    91201}
    92202
     203JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface, bool reload)
     204{
     205    TIMER(L"GetReplays");
     206    JSContext* cx = scriptInterface.GetContext();
     207    JSAutoRequest rq(cx);
     208    // Acquire replays
     209    JS::RootedObject replays(cx, ReloadReplayCache(scriptInterface, reload));
     210    // Remove entries without data
     211    JS::RootedObject replaysWithoutNullEntries(cx, JS_NewArrayObject(cx, 0));
     212    u32 replaysLength = 0;
     213    u32 i = 0;
     214    JS_GetArrayLength(cx, replays, &replaysLength);
     215    for (u32 j = 0; j < replaysLength; ++j)
     216    {
     217        JS::RootedValue replay(cx);
     218        JS_GetElement(cx, replays, j, &replay);
     219        if (scriptInterface.HasProperty(replay, "attribs"))
     220            JS_SetElement(cx, replaysWithoutNullEntries, i++, replay);
     221    }
     222    return JS::ObjectValue(*replaysWithoutNullEntries);
     223}
     224
    93225/**
    94226 * Move the cursor backwards until a newline was read or the beginning of the file was found.
    95227 * Either way the cursor points to the beginning of a newline.
     
    173305    return -1;
    174306}
    175307
    176 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)
     308JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory)
    177309{
     310    TIMER(L"LoadReplayData");
    178311    // The directory argument must not be constant, otherwise concatenating will fail
    179312    const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt";
    180313
     
    251384    scriptInterface.Eval("({})", &replayData);
    252385    scriptInterface.SetProperty(replayData, "file", replayFile);
    253386    scriptInterface.SetProperty(replayData, "directory", directory);
     387    scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize);
    254388    scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime));
    255389    scriptInterface.SetProperty(replayData, "attribs", attribs);
    256390    scriptInterface.SetProperty(replayData, "duration", duration);
     
    266400    return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK;
    267401}
    268402
    269 
    270403JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
    271404{
    272405    // Create empty JS object
     
    292425    return attribs;
    293426}
    294427
     428void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName)
     429{
     430    TIMER(L"AddReplayToCache");
     431    JSContext* cx = scriptInterface.GetContext();
     432    JSAutoRequest rq(cx);
     433
     434    JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName)));
     435
     436    if (replayData.isNull())
     437        return;
     438
     439    const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
     440    JS::RootedObject cachedReplaysObject(cx, JS_NewArrayObject(cx, 0));
     441
     442    if (FileExists(cacheFileName))
     443    {
     444        // Open cache file
     445        std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str());
     446
     447        // Read file into chacheStr
     448        CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>());
     449
     450        // Create empty JS object and parse the content of the cache into it
     451        JS::RootedValue cachedReplays(cx);
     452        scriptInterface.ParseJSON(cacheStr, &cachedReplays);
     453        SAFE_DELETE(cacheStream);
     454
     455        cachedReplaysObject = &cachedReplays.toObject();
     456    }
     457
     458    u32 cacheLength = 0;
     459    JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     460    JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData);
     461
     462    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*cachedReplaysObject));
     463    std::ofstream cacheStream(cacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     464    cacheStream << scriptInterface.StringifyJSON(&replaysRooted);
     465    cacheStream.close();
     466}
     467
    295468void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface)
    296469{
    297470    JSContext* cx = scriptInterface->GetContext();
  • source/ps/VisualReplay.h

     
    4242void StartVisualReplay(const CStrW& directory);
    4343
    4444/**
     45 * Reloads the replay cache
     46 *
     47 * @param scriptInterface the ScriptInterface in which to create the return data.
     48 * @param compareFileSize compare the directory name and the FileSize of the replays and the cache
     49 * @return cache entries
     50 */
     51JS::HandleObject ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize = false);
     52
     53/**
    4554 * Get a list of replays to display in the GUI.
    4655 *
    4756 * @param scriptInterface the ScriptInterface in which to create the return data.
    4857 * @return array of objects containing replay data
    4958 */
    50 JS::Value GetReplays(ScriptInterface& scriptInterface);
     59JS::Value GetReplays(ScriptInterface& scriptInterface, bool reload);
    5160
    5261/**
    5362 * Parses a commands.txt file and extracts metadata.
    5463 * Works similarly to CGame::LoadReplayData().
    5564 */
    56 JS::Value LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory);
     65JS::Value LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory);
    5766
    5867/**
    5968 * Permanently deletes the visual replay (including the parent directory)
     
    8392 */
    8493void SaveReplayMetadata(ScriptInterface* scriptInterface);
    8594
     95/**
     96* Adds a replay to the replayCache
     97*/
     98void AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName);
    8699}
    87100
    88101#endif