Ticket #3433: 3433_replay_cache_v1.6_wip.patch

File 3433_replay_cache_v1.6_wip.patch, 13.0 KB (added by Imarok, 8 years ago)
  • binaries/data/mods/public/gui/replaymenu/replay_actions.js

     
    127127    });
    128128}
    129129
     130function reloadCache()
     131{
     132    Engine.ReloadReplayCache();
     133
     134    let selected = Engine.GetGUIObjectByName("replaySelection").selected;
     135    Engine.SwitchGuiPage("page_replaymenu.xml", selected > 0 ?
     136    { "replaySelectionData": createReplaySelectionData(g_ReplaysFiltered[selected].directory) } : "");
     137}
     138
    130139/**
    131140 * Callback.
    132141 */
  • binaries/data/mods/public/gui/replaymenu/replay_menu.xml

     
    240240                <action on="Press">deleteReplayButtonPressed();</action>
    241241            </object>
    242242
     243            <!-- Reload Cache Button -->
     244            <object type="button" style="StoneButton" size="40%+25 0 57%+25 100%">
     245                <translatableAttribute id="caption">Reload Cache</translatableAttribute>
     246                <action on="Press">reloadCache();</action>
     247            </object>
     248
    243249            <!-- Summary Button -->
    244250            <object name="summaryButton" type="button" style="StoneButton" size="65%-50 0 82%-50 100%">
    245251                <translatableAttribute id="caption">Summary</translatableAttribute>
  • binaries/data/mods/public/gui/session/session.js

     
    526526
    527527    Engine.EndGame();
    528528
     529    // After game is ended
     530    if (!g_IsReplay)
     531        Engine.AddReplayToCache(replayDirectory);
     532
    529533    if (g_IsController && Engine.HasXmppClient())
    530534        Engine.SendUnregisterGame();
    531535
  • source/ps/scripting/JSInterface_VisualReplay.cpp

     
    5252{
    5353    return VisualReplay::GetReplayMetadata(pCxPrivate, directoryName);
    5454}
     55 
     56void JSI_VisualReplay::AddReplayToCache(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
     57{
     58    VisualReplay::AddReplayToCache(*(pCxPrivate->pScriptInterface), directoryName);
     59}
    5560
     61/*void JSI_VisualReplay::ReloadReplayCache(ScriptInterface::CxPrivate* pCxPrivate)
     62{
     63    VisualReplay::ReloadReplayCache(*(pCxPrivate->pScriptInterface));
     64}*/
     65
    5666void JSI_VisualReplay::RegisterScriptFunctions(ScriptInterface& scriptInterface)
    5767{
    5868    scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays");
     
    6171    scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayAttributes>("GetReplayAttributes");
    6272    scriptInterface.RegisterFunction<JS::Value, CStrW, &GetReplayMetadata>("GetReplayMetadata");
    6373    scriptInterface.RegisterFunction<bool, CStrW, &HasReplayMetadata>("HasReplayMetadata");
     74    scriptInterface.RegisterFunction<void, CStrW, &AddReplayToCache>("AddReplayToCache");
     75    //scriptInterface.RegisterFunction<void, &ReloadReplayCache>("ReloadReplayCache");
    6476}
  • source/ps/scripting/JSInterface_VisualReplay.h

     
    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);
     33    //void ReloadReplayCache(ScriptInterface::CxPrivate* pCxPrivate);
    3234    void RegisterScriptFunctions(ScriptInterface& scriptInterface);
    3335}
    3436
  • 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.
     65 * Load the replay cache and check if there are new/deleted ones
     66 * If so, update their data.
    6967 */
    7068JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface)
    7169{
     
    7371    JSContext* cx = scriptInterface.GetContext();
    7472    JSAutoRequest rq(cx);
    7573
    76     u32 i = 0;
     74    // Maps the filename onto the replay data
     75    std::map<CStr, JS::Heap<JS::Value>> cachedReplaysMap;
     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    bool cacheFound = FileExists(cacheFileName);
     82    // Load cache and create fileList
     83    if (cacheFound)
     84    {
     85        // Open cache file
     86        std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str());
     87
     88        // Read file into chacheStr
     89        CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>());
     90        SAFE_DELETE(cacheStream);
     91
     92        // Create empty JS object and parse the context of the cache into it
     93        JS::RootedValue cachedReplays(cx);
     94        scriptInterface.ParseJSON(cacheStr, &cachedReplays);
     95
     96        cachedReplaysObject = &cachedReplays.toObject();
     97
     98        // Create list of files included in the cache
     99        u32 cacheLength = 0;
     100        JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     101        for (u32 j = 0; j < cacheLength ; ++j)
     102        {
     103            JS::RootedValue replay(cx);
     104            JS_GetElement(cx, cachedReplaysObject, j, &replay);
     105
     106            JS::RootedValue directoryJS(cx);
     107            CStr directory;
     108            scriptInterface.GetProperty(replay, "directory", &directoryJS);
     109            scriptInterface.FromJSVal(cx, directoryJS, directory);
     110            cachedReplaysMap.emplace(directory, JS::Heap<JS::Value>(replay.get()));
     111        }
     112    }
     113
     114    JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
     115    bool newReplays = false;
     116    std::vector<JS::Value> copyFromOldCache;
    77117    DirectoryNames directories;
    78     JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
    79118
    80     if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK)
    81         for (OsPath& directory : directories)
     119    // Specifies where the next replay data should go in replays
     120    u32 i = 0;
     121    if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK)
     122        return JS::ObjectValue(*replays);
     123   
     124    for (OsPath& directory : directories)
     125    {
     126        if (SDL_QuitRequested())
     127            break;
     128
     129        std::map<CStr, JS::Heap<JS::Value>>::iterator it = cachedReplaysMap.find(directory.string8());
     130        bool isNew = true;
     131
     132        // There is already a replay with this directory name, so check if the size of the file matches too
     133        if (it != cachedReplaysMap.end())
    82134        {
    83             if (SDL_QuitRequested())
    84                 return JSVAL_NULL;
     135            JS::RootedValue fileSizeJS(cx);
     136            long fileSize;
     137            JS::RootedValue replay(cx, it->second);
     138            scriptInterface.GetProperty(replay, "fileSize", &fileSizeJS);
     139            scriptInterface.FromJSVal(cx, fileSizeJS, fileSize);
    85140
     141            CFileInfo fileInfo;
     142            GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
     143
     144            if (fileInfo.Size() == fileSize)
     145                isNew = false;
     146        }
     147
     148        if (isNew)
     149        {
    86150            JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
    87151            if (!replayData.isNull())
     152            {
    88153                JS_SetElement(cx, replays, i++, replayData);
     154                newReplays = true;
     155            }
    89156        }
     157        else
     158            copyFromOldCache.push_back(it->second);
     159    }
     160
     161    // No replay was changed, so just return the cache
     162    if (!newReplays && cachedReplaysMap.size() == cachedReplaysMap.size())
     163        return JS::ObjectValue(*cachedReplaysObject);
     164
     165    // Cache found, so copy the replays from the cache that are not deleted
     166    if (!copyFromOldCache.empty())
     167        for (JS::Value replay : copyFromOldCache)
     168        {
     169            JS::RootedValue replayRooted(cx, replay);
     170            JS_SetElement(cx, replays, i++, replayRooted);
     171        }
     172
     173    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays));
     174    std::ofstream stream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     175    stream << scriptInterface.StringifyJSON(&replaysRooted);
     176    stream.close();
     177
     178    wunlink(cacheFileName);
     179    if (wrename(tempCacheFileName, cacheFileName))
     180        LOGERROR("Could not store the replay cache");
     181
    90182    return JS::ObjectValue(*replays);
    91183}
    92184
     
    173265    return -1;
    174266}
    175267
    176 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)
     268JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory)
    177269{
    178270    // The directory argument must not be constant, otherwise concatenating will fail
    179271    const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt";
     
    251343    scriptInterface.Eval("({})", &replayData);
    252344    scriptInterface.SetProperty(replayData, "file", replayFile);
    253345    scriptInterface.SetProperty(replayData, "directory", directory);
     346    scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize);
    254347    scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime));
    255348    scriptInterface.SetProperty(replayData, "attribs", attribs);
    256349    scriptInterface.SetProperty(replayData, "duration", duration);
     
    266359    return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK;
    267360}
    268361
    269 
    270362JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
    271363{
    272364    // Create empty JS object
     
    292384    return attribs;
    293385}
    294386
     387void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName)
     388{
     389    TIMER(L"AddReplayToCache");
     390    JSContext* cx = scriptInterface.GetContext();
     391    JSAutoRequest rq(cx);
     392
     393    JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName)));
     394
     395    if (replayData.isNull())
     396        return;
     397
     398    const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
     399    JS::RootedObject cachedReplaysObject(cx, JS_NewArrayObject(cx, 0));
     400
     401    if (FileExists(cacheFileName))
     402    {
     403        // Open cache file
     404        std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str());
     405
     406        // Read file into chacheStr
     407        CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>());
     408
     409        // Create empty JS object and parse the context of the cache into it
     410        JS::RootedValue cachedReplays(cx);
     411        scriptInterface.ParseJSON(cacheStr, &cachedReplays);
     412        SAFE_DELETE(cacheStream);
     413
     414        JS::RootedObject temp(cx, &cachedReplays.toObject());
     415        cachedReplaysObject = temp;
     416    }
     417   
     418    u32 cacheLength = 0;
     419    JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     420    JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData);
     421
     422    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*cachedReplaysObject));
     423    std::ofstream cacheStream(cacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     424    cacheStream << scriptInterface.StringifyJSON(&replaysRooted);
     425    cacheStream.close();
     426}
     427
     428/*void VisualReplay::ReloadReplayCache(ScriptInterface& scriptInterface)
     429{
     430    JSContext* cx = scriptInterface.GetContext();
     431    JSAutoRequest rq(cx);
     432
     433    JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
     434    DirectoryNames directories;
     435    u32 i = 0;
     436
     437    if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK)
     438        return;
     439
     440    for (OsPath& directory : directories)
     441    {
     442        if (SDL_QuitRequested())
     443            break;
     444
     445        JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
     446        if (!replayData.isNull())
     447            JS_SetElement(cx, replays, i++, replayData);
     448    }
     449
     450    const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
     451    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays));
     452    std::ofstream stream(cacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     453    stream << scriptInterface.StringifyJSON(&replaysRooted);
     454    stream.close();
     455}*/
     456
    295457void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface)
    296458{
    297459    JSContext* cx = scriptInterface->GetContext();
  • source/ps/VisualReplay.h

     
    5353 * Parses a commands.txt file and extracts metadata.
    5454 * Works similarly to CGame::LoadReplayData().
    5555 */
    56 JS::Value LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory);
     56JS::Value LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory);
    5757
    5858/**
    5959 * Permanently deletes the visual replay (including the parent directory)
     
    8383 */
    8484void SaveReplayMetadata(ScriptInterface* scriptInterface);
    8585
     86/**
     87* Saves the metadata from the session to metadata.json
     88*/
     89void AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName);
     90
     91/**
     92 * Reloads the replay cache
     93 *
     94void ReloadReplayCache(ScriptInterface& scriptInterface);*/
     95
    8696}
    8797
    8898#endif