Ticket #3433: 3433_replay_cache_v1.8.1.patch

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

right filename

  • 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

     
    583583
    584584    Engine.EndGame();
    585585
     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
    586591    if (g_IsController && Engine.HasXmppClient())
    587592        Engine.SendUnregisterGame();
    588593
  • 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

     
    6161    g_Game->StartVisualReplay(replayFile.string8());
    6262}
    6363
     64bool 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
     100void 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
    64119/**
    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*/
     123JS::HandleObject VisualReplay::ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize)
    71124{
    72     TIMER(L"GetReplays");
     125    TIMER(L"ReloadReplayCache");
    73126    JSContext* cx = scriptInterface.GetContext();
    74127    JSAutoRequest rq(cx);
     128    // Maps the filename onto the index and size
     129    std::map<CStr, std::pair<u32, u32>> fileList;
    75130
    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));
    77156    DirectoryNames directories;
    78     JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
    79157
    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)
    82168        {
    83169            if (SDL_QuitRequested())
    84                 return JSVAL_NULL;
     170                return replays;
    85171
    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                }
    88199                JS_SetElement(cx, replays, i++, replayData);
     200                newReplays = true;
     201            }
     202            else
     203                copyFromOldCache.push_back(it->second.first);
    89204        }
    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;
    91230}
    92231
     232JS::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
    93254/**
    94255 * Move the cursor backwards until a newline was read or the beginning of the file was found.
    95256 * Either way the cursor points to the beginning of a newline.
     
    173334    return -1;
    174335}
    175336
    176 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)
     337JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory)
    177338{
     339    TIMER(L"LoadReplayData");
    178340    // The directory argument must not be constant, otherwise concatenating will fail
    179341    const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt";
    180342
     
    251413    scriptInterface.Eval("({})", &replayData);
    252414    scriptInterface.SetProperty(replayData, "file", replayFile);
    253415    scriptInterface.SetProperty(replayData, "directory", directory);
     416    scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize);
    254417    scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime));
    255418    scriptInterface.SetProperty(replayData, "attribs", attribs);
    256419    scriptInterface.SetProperty(replayData, "duration", duration);
     
    266429    return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK;
    267430}
    268431
    269 
    270432JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
    271433{
    272434    // Create empty JS object
     
    292454    return attribs;
    293455}
    294456
     457void 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
    295478void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface)
    296479{
    297480    JSContext* cx = scriptInterface->GetContext();
  • source/ps/VisualReplay.h

     
    4242void StartVisualReplay(const CStrW& directory);
    4343
    4444/**
     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 */
     51bool 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 */
     59void 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 */
     68JS::HandleObject ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize = false);
     69
     70/**
    4571 * Get a list of replays to display in the GUI.
    4672 *
    4773 * @param scriptInterface the ScriptInterface in which to create the return data.
    4874 * @return array of objects containing replay data
    4975 */
    50 JS::Value GetReplays(ScriptInterface& scriptInterface);
     76JS::Value GetReplays(ScriptInterface& scriptInterface, bool reload);
    5177
    5278/**
    5379 * Parses a commands.txt file and extracts metadata.
    5480 * Works similarly to CGame::LoadReplayData().
    5581 */
    56 JS::Value LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory);
     82JS::Value LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory);
    5783
    5884/**
    5985 * Permanently deletes the visual replay (including the parent directory)
     
    83109 */
    84110void SaveReplayMetadata(ScriptInterface* scriptInterface);
    85111
     112/**
     113* Adds a replay to the replayCache
     114*/
     115void AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName);
    86116}
    87117
    88118#endif