Ticket #3433: 3433_replay_cache_v1.6_vector.patch

File 3433_replay_cache_v1.6_vector.patch, 10.2 KB (added by Imarok, 8 years ago)

Use a vector instead of a map

  • binaries/data/mods/public/gui/session/session.js

     
    537537
    538538    Engine.EndGame();
    539539
     540    // After the replay file was closed in EndGame
     541    // Done here to keep EndGame small
     542    if (!g_IsReplay)
     543        Engine.AddReplayToCache(replayDirectory);
     544
    540545    if (g_IsController && Engine.HasXmppClient())
    541546        Engine.SendUnregisterGame();
    542547
  • source/ps/scripting/JSInterface_VisualReplay.cpp

     
    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{
    5863    scriptInterface.RegisterFunction<JS::Value, &GetReplays>("GetReplays");
     
    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

     
    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.
     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 index and size
     75    std::vector<std::pair<CStr, std::pair<u32, u64>>> 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        // Open cache file
     84        std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str());
     85
     86        // Read file into chacheStr
     87        CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>());
     88
     89        // Create empty JS object and parse the context of the cache into it
     90        JS::RootedValue cachedReplays(cx);
     91        scriptInterface.ParseJSON(cacheStr, &cachedReplays);
     92        SAFE_DELETE(cacheStream);
     93
     94        cachedReplaysObject = &cachedReplays.toObject();
     95
     96        // Create list of files included in the cache
     97        u32 cacheLength = 0;
     98        JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     99        for (u32 j = 0; j < cacheLength ; ++j)
     100        {
     101            JS::RootedValue replay(cx);
     102            JS_GetElement(cx, cachedReplaysObject, j, &replay);
     103
     104            JS::RootedValue file(cx);
     105            CStr fileName;
     106            u32 fileSize;
     107            scriptInterface.GetProperty(replay, "directory", fileName);
     108            scriptInterface.GetProperty(replay, "fileSize", fileSize);
     109
     110            fileList.push_back(std::make_pair(fileName, std::make_pair(j, fileSize)));
     111        }
     112        std::sort(fileList.begin(), fileList.end());
     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 JS::ObjectValue(*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    for (const OsPath& directory : directories)
     127    {
     128        if (SDL_QuitRequested())
     129            break;
     130
     131        bool isNew = true;
     132        std::pair<CStr, std::pair<u32, u64>> searchVal = std::make_pair(directory.string8(), std::make_pair(0, 0));
     133        std::vector<std::pair<CStr, std::pair<u32, u64>>>::iterator it = lower_bound(fileList.begin(), fileList.end(), searchVal);
     134        // directory is in fileList
     135        if (it != fileList.end() && it->first == directory.string8())
    82136        {
    83             if (SDL_QuitRequested())
    84                 return JSVAL_NULL;
     137            CFileInfo fileInfo;
     138            GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
     139            if (fileInfo.Size() == it->second.second)
     140                isNew = false;
     141        }
    85142
     143        if (isNew)
     144        {
    86145            JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
    87146            if (!replayData.isNull())
     147            {
    88148                JS_SetElement(cx, replays, i++, replayData);
     149                newReplays = true;
     150            }
    89151        }
     152        else
     153            copyFromOldCache.push_back(it->second.first);
     154    }
     155
     156    if (!newReplays && fileList.empty())
     157        return JS::ObjectValue(*replays);
     158    // No replay was changed, so just return the cache
     159    if (!newReplays && fileList.size() == copyFromOldCache.size())
     160        return JS::ObjectValue(*cachedReplaysObject);
     161
     162    // Copy the replays from the old cache that are not deleted
     163    if (!copyFromOldCache.empty())
     164        for (u32 j : copyFromOldCache)
     165        {
     166            JS::RootedValue replay(cx);
     167            JS_GetElement(cx, cachedReplaysObject, j, &replay);
     168            JS_SetElement(cx, replays, i++, replay);
     169        }
     170
     171    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays));
     172    std::ofstream stream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     173    stream << scriptInterface.StringifyJSON(&replaysRooted);
     174    stream.close();
     175
     176    wunlink(cacheFileName);
     177    if (wrename(tempCacheFileName, cacheFileName))
     178        LOGERROR("Could not store the replay cache");
     179
    90180    return JS::ObjectValue(*replays);
    91181}
    92182
     
    173263    return -1;
    174264}
    175265
    176 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)
     266JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory)
    177267{
    178268    // The directory argument must not be constant, otherwise concatenating will fail
    179269    const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt";
     
    251341    scriptInterface.Eval("({})", &replayData);
    252342    scriptInterface.SetProperty(replayData, "file", replayFile);
    253343    scriptInterface.SetProperty(replayData, "directory", directory);
     344    scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize);
    254345    scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime));
    255346    scriptInterface.SetProperty(replayData, "attribs", attribs);
    256347    scriptInterface.SetProperty(replayData, "duration", duration);
     
    266357    return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK;
    267358}
    268359
    269 
    270360JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
    271361{
    272362    // Create empty JS object
     
    292382    return attribs;
    293383}
    294384
     385void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName)
     386{
     387    TIMER(L"AddReplayToCache");
     388    JSContext* cx = scriptInterface.GetContext();
     389    JSAutoRequest rq(cx);
     390
     391    JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName)));
     392
     393    if (replayData.isNull())
     394        return;
     395
     396    const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
     397    JS::RootedObject cachedReplaysObject(cx, JS_NewArrayObject(cx, 0));
     398
     399    if (FileExists(cacheFileName))
     400    {
     401        // Open cache file
     402        std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str());
     403
     404        // Read file into chacheStr
     405        CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>());
     406
     407        // Create empty JS object and parse the content of the cache into it
     408        JS::RootedValue cachedReplays(cx);
     409        scriptInterface.ParseJSON(cacheStr, &cachedReplays);
     410        SAFE_DELETE(cacheStream);
     411
     412        cachedReplaysObject = &cachedReplays.toObject();
     413    }
     414
     415    u32 cacheLength = 0;
     416    JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     417    JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData);
     418
     419    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*cachedReplaysObject));
     420    std::ofstream cacheStream(cacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     421    cacheStream << scriptInterface.StringifyJSON(&replaysRooted);
     422    cacheStream.close();
     423}
     424
    295425void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface)
    296426{
    297427    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* Adds a replay to the replayCache
     88*/
     89void AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName);
    8690}
    8791
    8892#endif