Ticket #3433: 3433_replay_cache_v1.6.patch

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

Remove the reloadCache functionality. Compare fileName and fileSize to decide if the file changed. Some minor fixes

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

     
    529529
    530530    Engine.EndGame();
    531531
     532    // After the replay file was closed in EndGame
     533    // Done here to keep EndGame small
     534    if (!g_IsReplay)
     535        Engine.AddReplayToCache(replayDirectory);
     536
    532537    if (g_IsController && Engine.HasXmppClient())
    533538        Engine.SendUnregisterGame();
    534539
  • 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::map<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.emplace(fileName, std::make_pair(j, fileSize));
     111        }
     112    }
     113
     114    JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
    77115    DirectoryNames directories;
    78     JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0));
    79116
    80     if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK)
    81         for (OsPath& directory : directories)
     117    if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK)
     118        return JS::ObjectValue(*replays);
     119
     120    bool newReplays = false;
     121    std::vector<u32> copyFromOldCache;
     122    // Specifies where the next replay data should go in replays
     123    u32 i = 0;
     124
     125    for (const OsPath& directory : directories)
     126    {
     127        if (SDL_QuitRequested())
     128            break;
     129
     130        bool isNew = true;
     131        std::map<CStr, std::pair<u32, u64>>::iterator it = fileList.find(directory.string8());
     132        // directory is in fileList
     133        if (it != fileList.end())
    82134        {
    83             if (SDL_QuitRequested())
    84                 return JSVAL_NULL;
     135            CFileInfo fileInfo;
     136            GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo);
     137            if (fileInfo.Size() == it->second.second)
     138                isNew = false;
     139        }
    85140
     141        if (isNew)
     142        {
    86143            JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory));
    87144            if (!replayData.isNull())
     145            {
    88146                JS_SetElement(cx, replays, i++, replayData);
     147                newReplays = true;
     148            }
    89149        }
     150        else
     151            copyFromOldCache.push_back(it->second.first);
     152    }
     153
     154    if (!newReplays && fileList.empty())
     155        return JS::ObjectValue(*replays);
     156    // No replay was changed, so just return the cache
     157    if (!newReplays && fileList.size() == copyFromOldCache.size())
     158        return JS::ObjectValue(*cachedReplaysObject);
     159
     160    // Copy the replays from the old cache that are not deleted
     161    if (!copyFromOldCache.empty())
     162        for (u32 j : copyFromOldCache)
     163        {
     164            JS::RootedValue replay(cx);
     165            JS_GetElement(cx, cachedReplaysObject, j, &replay);
     166            JS_SetElement(cx, replays, i++, replay);
     167        }
     168
     169    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays));
     170    std::ofstream stream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     171    stream << scriptInterface.StringifyJSON(&replaysRooted);
     172    stream.close();
     173
     174    wunlink(cacheFileName);
     175    if (wrename(tempCacheFileName, cacheFileName))
     176        LOGERROR("Could not store the replay cache");
     177
    90178    return JS::ObjectValue(*replays);
    91179}
    92180
     
    173261    return -1;
    174262}
    175263
    176 JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory)
     264JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory)
    177265{
    178266    // The directory argument must not be constant, otherwise concatenating will fail
    179267    const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt";
     
    251339    scriptInterface.Eval("({})", &replayData);
    252340    scriptInterface.SetProperty(replayData, "file", replayFile);
    253341    scriptInterface.SetProperty(replayData, "directory", directory);
     342    scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize);
    254343    scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime));
    255344    scriptInterface.SetProperty(replayData, "attribs", attribs);
    256345    scriptInterface.SetProperty(replayData, "duration", duration);
     
    266355    return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK;
    267356}
    268357
    269 
    270358JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName)
    271359{
    272360    // Create empty JS object
     
    292380    return attribs;
    293381}
    294382
     383void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName)
     384{
     385    TIMER(L"AddReplayToCache");
     386    JSContext* cx = scriptInterface.GetContext();
     387    JSAutoRequest rq(cx);
     388
     389    JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName)));
     390
     391    if (replayData.isNull())
     392        return;
     393
     394    const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json";
     395    JS::RootedObject cachedReplaysObject(cx, JS_NewArrayObject(cx, 0));
     396
     397    if (FileExists(cacheFileName))
     398    {
     399        // Open cache file
     400        std::istream* cacheStream = new std::ifstream(cacheFileName.string8().c_str());
     401
     402        // Read file into chacheStr
     403        CStr cacheStr((std::istreambuf_iterator<char>(*cacheStream)), std::istreambuf_iterator<char>());
     404
     405        // Create empty JS object and parse the content of the cache into it
     406        JS::RootedValue cachedReplays(cx);
     407        scriptInterface.ParseJSON(cacheStr, &cachedReplays);
     408        SAFE_DELETE(cacheStream);
     409
     410        cachedReplaysObject = &cachedReplays.toObject();
     411    }
     412
     413    u32 cacheLength = 0;
     414    JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength);
     415    JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData);
     416
     417    JS::RootedValue replaysRooted(cx, JS::ObjectValue(*cachedReplaysObject));
     418    std::ofstream cacheStream(cacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc);
     419    cacheStream << scriptInterface.StringifyJSON(&replaysRooted);
     420    cacheStream.close();
     421}
     422
    295423void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface)
    296424{
    297425    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