Changeset View
Standalone View
source/ps/VisualReplay.cpp
/* Copyright (C) 2016 Wildfire Games. | /* Copyright (C) 2016 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
elexis: this file needs rebase following https://code.wildfiregames.com/rP19200#5c8659fc
(So reverting… | |||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 0 A.D. is free software: you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
Show All 20 Lines | |||||
#include "ps/GameSetup/Paths.h" | #include "ps/GameSetup/Paths.h" | ||||
#include "ps/Mod.h" | #include "ps/Mod.h" | ||||
#include "ps/Pyrogenesis.h" | #include "ps/Pyrogenesis.h" | ||||
#include "ps/Replay.h" | #include "ps/Replay.h" | ||||
#include "ps/Util.h" | #include "ps/Util.h" | ||||
#include "scriptinterface/ScriptInterface.h" | #include "scriptinterface/ScriptInterface.h" | ||||
/** | /** | ||||
* Filter too short replays (value in seconds). | * Filter too short replays (value in seconds). | ||||
Done Inline ActionsThat doesn't need to be global, only in the scope that uses it (see my proposal for examplE). elexis: That doesn't need to be global, only in the scope that uses it (see my proposal for examplE). | |||||
*/ | */ | ||||
const u8 minimumReplayDuration = 3; | const u8 minimumReplayDuration = 3; | ||||
OsPath VisualReplay::GetDirectoryName() | OsPath VisualReplay::GetDirectoryName() | ||||
{ | { | ||||
Done Inline Actionsg_Foo only used for actual globals afaik and it won't be accessible unless declaring it with the external keyword and it will be impossible when using the static keyword afaik, so +static -g_ elexis: g_Foo only used for actual globals afaik and it won't be accessible unless declaring it with… | |||||
const Paths paths(g_args); | const Paths paths(g_args); | ||||
return OsPath(paths.UserData() / "replays" / engine_version); | return OsPath(paths.UserData() / "replays" / engine_version); | ||||
} | } | ||||
void VisualReplay::StartVisualReplay(const CStrW& directory) | void VisualReplay::StartVisualReplay(const CStrW& directory) | ||||
{ | { | ||||
ENSURE(!g_NetServer); | ENSURE(!g_NetServer); | ||||
ENSURE(!g_NetClient); | ENSURE(!g_NetClient); | ||||
ENSURE(!g_Game); | ENSURE(!g_Game); | ||||
const OsPath replayFile = VisualReplay::GetDirectoryName() / directory / L"commands.txt"; | const OsPath replayFile = VisualReplay::GetDirectoryName() / directory / L"commands.txt"; | ||||
if (!FileExists(replayFile)) | if (!FileExists(replayFile)) | ||||
return; | return; | ||||
g_Game = new CGame(false, false); | g_Game = new CGame(false, false); | ||||
g_Game->StartVisualReplay(replayFile.string8()); | g_Game->StartVisualReplay(replayFile.string8()); | ||||
} | } | ||||
bool VisualReplay::ReadCacheFile(ScriptInterface& scriptInterface, JS::MutableHandleObject cachedReplaysObject) | |||||
{ | |||||
TIMER(L"ReadCacheFile"); | |||||
Done Inline ActionsCan be removed IMO, too noisy, only opening of one file (which may hang if the HDD hangs. Still nothing we can do about that with code then.) elexis: Can be removed IMO, too noisy, only opening of one file (which may hang if the HDD hangs. Still… | |||||
JSContext* cx = scriptInterface.GetContext(); | |||||
JSAutoRequest rq(cx); | |||||
const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json"; | |||||
if (!FileExists(cacheFileName)) | |||||
return false; | |||||
Done Inline Actionsnot really needed elexis: not really needed | |||||
// Open cache file | |||||
Not Done Inline ActionsThat sounds a lot like suffering from the same breakage reported in #4320, i.e. should use an OsString(). Should actually be tested by returning something like replêys in GetDirectoryName. elexis: That sounds a lot like suffering from the same breakage reported in #4320, i.e. should use an… | |||||
std::ifstream cacheStream(cacheFileName.string8().c_str()); | |||||
// Read file into cacheStr | |||||
Done Inline ActionsWe only need one of those 2 comments. elexis: We only need one of those 2 comments. | |||||
CStr cacheStr((std::istreambuf_iterator<char>(cacheStream)), std::istreambuf_iterator<char>()); | |||||
cacheStream.close(); | |||||
// Create empty JS object and parse the context of the cache into it | |||||
Done Inline ActionsThe JSON is parsed. The context is the spider monkey environment which stores the currently known variables and functions. Comment doesn't really state anything besides that what is known from the function title. elexis: The JSON is parsed. The context is the spider monkey environment which stores the currently… | |||||
JS::RootedValue cachedReplays(cx); | |||||
if (!scriptInterface.ParseJSON(cacheStr, &cachedReplays)) | |||||
{ | |||||
LOGERROR("The replay cache file is corrupted, it will be deleted"); | |||||
Done Inline ActionsLOGERROR should be used for serious things. Use LOGWARNING if you insist on notifying the user (which doesn't really seem necessary to me since noone will legitimately edit that file. It might only break when writing it while the disk has no more space left.). elexis: `LOGERROR` should be used for serious things. Use `LOGWARNING` if you insist on notifying the… | |||||
wunlink(cacheFileName); | |||||
return false; | |||||
Not Done Inline ActionsBit awkward to have one yellow warning and two red errors, but this still isn't an actual error. ERROR: JS_ParseJSON failed! ERROR: SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data WARNING: The replay cache file is corrupted, it will be deleted elexis: Bit awkward to have one yellow warning and two red errors, but this still isn't an actual error. | |||||
} | |||||
Not Done Inline ActionsOsString() elexis: `OsString()` | |||||
cachedReplaysObject.set(&cachedReplays.toObject()); | |||||
if (!JS_IsArrayObject(cx, cachedReplaysObject)) | |||||
{ | |||||
LOGERROR("The replay cache file is corrupted, it will be deleted"); | |||||
Done Inline ActionsSame. elexis: Same. | |||||
wunlink(cacheFileName); | |||||
Done Inline ActionsUnneeded and noisy too IMO elexis: Unneeded and noisy too IMO | |||||
return false; | |||||
} | |||||
return true; | |||||
Done Inline ActionsLooks like you could avoid duplication here by not inverting the conditions. elexis: Looks like you could avoid duplication here by not inverting the conditions. | |||||
} | |||||
Not Done Inline ActionsOsString elexis: OsString | |||||
void VisualReplay::StoreCacheFile(ScriptInterface& scriptInterface, JS::HandleObject replays) | |||||
{ | |||||
TIMER(L"StoreCacheFile"); | |||||
Not Done Inline ActionsOsString elexis: OsString | |||||
JSContext* cx = scriptInterface.GetContext(); | |||||
Not Done Inline ActionsOsString elexis: OsString | |||||
JSAutoRequest rq(cx); | |||||
const OsPath cacheFileName = GetDirectoryName() / L"replayCache.json"; | |||||
Done Inline ActionsAs far as I'm concerned it is good practice to move hardcoded constants to the top of the file. elexis: As far as I'm concerned it is good practice to move hardcoded constants to the top of the file. | |||||
const OsPath tempCacheFileName = GetDirectoryName() / L"replayCache_temp.json"; | |||||
JS::RootedValue replaysRooted(cx, JS::ObjectValue(*replays)); | |||||
std::ofstream cacheStream(tempCacheFileName.string8().c_str(), std::ofstream::out | std::ofstream::trunc); | |||||
cacheStream << scriptInterface.StringifyJSON(&replaysRooted); | |||||
cacheStream.close(); | |||||
wunlink(cacheFileName); | |||||
if (wrename(tempCacheFileName, cacheFileName)) | |||||
LOGERROR("Could not store the replay cache"); | |||||
Done Inline ActionsThat error seems more legitimate, since the file system couldn't be written to. elexis: That error seems more legitimate, since the file system couldn't be written to. | |||||
} | |||||
/** | /** | ||||
* Load all replays found in the directory. | * Load the replay cache and check if there are new/deleted ones | ||||
* | * If so, update their data. | ||||
Done Inline ActionsThe replays aren't updated, only the cache. elexis: The replays aren't updated, only the cache. | |||||
Not Done Inline ActionsActually we don't have doxygen comments here, but in the header file (unless the function only exists in the cpp file). Also this doxygen comment differs from the one in the header. elexis: Actually we don't have doxygen comments here, but in the header file (unless the function only… | |||||
Done Inline ActionsSee above, no comments in .cpp but .h elexis: See above, no comments in .cpp but .h | |||||
* Since files are spread across the harddisk, | |||||
* loading hundreds of them can consume a lot of time. | |||||
*/ | */ | ||||
JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface) | JS::HandleObject VisualReplay::ReloadReplayCache(ScriptInterface& scriptInterface, bool compareFileSize) | ||||
Done Inline Actions( Merging those 2 functions doesn't seem to be nicely doable because this one contains return statements which would have to be compensated with ugly scopes. elexis: (
Since this is only called locally, could be made 'private' of that file (i.e. not a namespace… | |||||
{ | { | ||||
TIMER(L"GetReplays"); | TIMER(L"ReloadReplayCache"); | ||||
Done Inline ActionsThat can be kept since it is guaranteed to consume a lot of time if many unknown replays are added. elexis: That can be kept since it is guaranteed to consume a lot of time if many unknown replays are… | |||||
JSContext* cx = scriptInterface.GetContext(); | JSContext* cx = scriptInterface.GetContext(); | ||||
JSAutoRequest rq(cx); | JSAutoRequest rq(cx); | ||||
// Maps the filename onto the index and size | |||||
Done Inline ActionsComment not needed anymore when using the readable types elexis: Comment not needed anymore when using the readable types | |||||
std::map<CStr, std::pair<u32, u32>> fileList; | |||||
Done Inline ActionsSince the type is used at least twice, a typedef would be nice, like typedef std::map<OsPath, std::pair<u32, off_t>> replayCacheMap; elexis: Since the type is used at least twice, a `typedef` would be nice, like `typedef std::map<OsPath… | |||||
Done Inline ActionsDon't think we really need a typedef here ^^ Imarok: Don't think we really need a typedef here ^^ | |||||
Done Inline ActionsNo, but it were nice to not repeat the type right? And the compiler will inline it anyway elexis: No, but it were nice to not repeat the type right? And the compiler will inline it anyway | |||||
JS::RootedObject cachedReplaysObject(cx); | |||||
Done Inline Actionsduplicate \n elexis: duplicate \n | |||||
if (ReadCacheFile(scriptInterface, &cachedReplaysObject)) | |||||
{ | |||||
Done Inline ActionsUnneeded, noisy elexis: Unneeded, noisy | |||||
TIMER(L"ReloadReplayCache: create fileList"); | |||||
// Create list of files included in the cache | |||||
Done Inline ActionsCorrect to use u32, because 2^32-1 is the greatest number of elements a JS array can hold. elexis: Correct to use u32, because 2^32-1 is the greatest number of elements a [[https://developer. | |||||
u32 cacheLength = 0; | |||||
JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength); | |||||
for (u32 j = 0; j < cacheLength; ++j) | |||||
Not Done Inline ActionsWould be nice if someone breaks this by storing more than 4 billion replays :D elexis: Would be nice if someone breaks this by storing more than 4 billion replays :D | |||||
{ | |||||
JS::RootedValue replay(cx); | |||||
JS_GetElement(cx, cachedReplaysObject, j, &replay); | |||||
JS::RootedValue file(cx); | |||||
CStr fileName; | |||||
u32 fileSize; | |||||
scriptInterface.GetProperty(replay, "directory", fileName); | |||||
scriptInterface.GetProperty(replay, "fileSize", fileSize); | |||||
Done Inline ActionsThis C++11 doesn't appear to be saving anything in comparison to fileList[fileName] = ... elexis: This C++11 doesn't appear to be saving anything in comparison to fileList[fileName] = ... | |||||
fileList.emplace(fileName, std::make_pair(j, fileSize)); | |||||
} | |||||
} | |||||
u32 i = 0; | |||||
DirectoryNames directories; | |||||
JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0)); | JS::RootedObject replays(cx, JS_NewArrayObject(cx, 0)); | ||||
DirectoryNames directories; | |||||
if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) != INFO::OK) | |||||
return replays; | |||||
if (GetDirectoryEntries(GetDirectoryName(), NULL, &directories) == INFO::OK) | bool newReplays = false; | ||||
for (OsPath& directory : directories) | std::vector<u32> copyFromOldCache; | ||||
// Specifies where the next replay data should go in replays | |||||
Done Inline Actions// Specifies whether the next replay file should be kept elexis: // Specifies whether the next replay file should be kept | |||||
u32 i = 0; | |||||
{ | |||||
TIMER(L"ReloadReplayCache: Load data"); | |||||
Done Inline ActionsNot needed elexis: Not needed | |||||
for (const OsPath& directory : directories) | |||||
{ | { | ||||
Done Inline ActionsWhy this scope? seems unneeded. Sounds like it came from having had a TIMER in there elexis: Why this scope? seems unneeded. Sounds like it came from having had a TIMER in there | |||||
if (SDL_QuitRequested()) | if (SDL_QuitRequested()) | ||||
Done Inline ActionsWhy is the filesize u32? It should be double so that we are not bound to 4GB but to 2^53 IMO and it's consistent with the other places. elexis: Why is the filesize u32? It should be `double` so that we are not bound to 4GB but to 2^53 IMO… | |||||
return JSVAL_NULL; | return replays; | ||||
Done Inline ActionsIf I recall correctly, we had talked about the use case of having a break instead of return here long before someone suggested to use a return here. The idea is that if the user has tons of replays and an old school harddisk with files scattered wildly, he can wait 30 seconds for replays to load, then press Alt+F4 after losing patience and then not lose the passed waiting time as the progress has been saved. If we return immediately, the progress will be lost. You might also recall the course of action that we take when changing code back to an earlier form because we forgot an (hint: undocumented) edge case / use case. elexis: If I recall correctly, we had talked about the use case of having a `break` instead of `return`… | |||||
Not Done Inline Actionstrue, I forgot about that :D Imarok: true, I forgot about that :D | |||||
Done Inline ActionsWhenever having to correct ones own mistake because one didn't think of all the cases, the consequence must be to add a comment preventing the repetition of that mistake and preventing someone else of stumbling over that. elexis: Whenever having to correct ones own mistake because one didn't think of all the cases, the… | |||||
Done Inline ActionsWe elexis: We | |||||
bool isNew = true; | |||||
std::map<CStr, std::pair<u32, u32>>::iterator it = fileList.find(directory.string8()); | |||||
Done Inline Actionsdon't want to lose -> want to save elexis: don't want to lose -> want to save
pretty fast part not needed | |||||
// directory is in fileList | |||||
if (it != fileList.end()) | |||||
{ | |||||
if (compareFileSize) | |||||
Done Inline Actionsu32 filesize: In file_system.h we see the filesize returned is of the type off_t, not exactly specified by posix. Using std::time_t and then converting it to double in the scriptInterface.SetProperty calls is what we do in the savegame & replay timestamp code, because double is equivalent to the only JS number type used. The greatest number is 2^53 iirc (i.e. holds much more than 2^32, but still less than 2^64). ps/Replay.cpp: m_ScriptInterface.SetProperty(attribs, "timestamp", (double)std::time(nullptr)); ps/SavedGame.cpp: simulation.GetScriptInterface().SetProperty(metadata, "time", (double)now); elexis: `u32 filesize`:
Better use the actual type returned than converting things needlessly (and… | |||||
Not Done Inline ActionsWhy not remove the`string8` conversion and use the OsPath ref from that DirectoryNames vector? elexis: Why not remove the`string8` conversion and use the `OsPath` ref from that `DirectoryNames`… | |||||
{ | |||||
Done Inline Actionsshould be self-evident elexis: should be self-evident | |||||
CFileInfo fileInfo; | |||||
GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo); | |||||
Done Inline ActionsThat commands.txt might be moved to a const as well (wchar_t array I guess). elexis: That `commands.txt` might be moved to a const as well (`wchar_t` array I guess). | |||||
Not Done Inline ActionsIf so, you should use one global const for VisualReplay and Replay, but this is out of this tickets scope. Imarok: If so, you should use one global const for `VisualReplay` and `Replay`, but this is out of this… | |||||
if ((u32)fileInfo.Size() == it->second.second) | |||||
isNew = false; | |||||
} | |||||
else | |||||
isNew = false; | |||||
Done Inline ActionsRemove the conversion then elexis: Remove the conversion then | |||||
} | |||||
if (isNew) | |||||
{ | |||||
JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); | JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, directory)); | ||||
if (!replayData.isNull()) | if (replayData.isNull()) | ||||
{ | |||||
CFileInfo fileInfo; | |||||
GetFileInfo(GetDirectoryName() / directory / L"commands.txt", &fileInfo); | |||||
scriptInterface.Eval("({})", &replayData); | |||||
Done Inline ActionsAren't these Eval calls creating empty objects a cheap copy of foo = JS_NewArrayObject(cx, 0)? https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS_NewArrayObject elexis: Aren't these `Eval` calls creating empty objects a cheap copy of `foo = JS_NewArrayObject(cx… | |||||
Not Done Inline ActionsNo, it's an Object and no Array. Imarok: No, it's an Object and no Array.
And this `scriptInterface.Eval("({})", &foo);` is done… | |||||
scriptInterface.SetProperty(replayData, "directory", directory); | |||||
Done Inline ActionsDidn't we say something about the 4GB limitation and what c++ type resembles the JS number type? elexis: Didn't we say something about the 4GB limitation and what c++ type resembles the JS number type? | |||||
scriptInterface.SetProperty(replayData, "fileSize", (u32)fileInfo.Size()); | |||||
} | |||||
Done Inline ActionsHard to read with only 2 spaces replacing tabs, but wouldn't we save one line if this is false by default and only set to true if the conditions are met? (Even merging two if statements?) elexis: Hard to read with only 2 spaces replacing tabs, but wouldn't we save one line if this is false… | |||||
Not Done Inline ActionsNo. The length would be the same(15 lines), but the readability would be worse... Imarok: No. The length would be the same(15 lines), but the readability would be worse... | |||||
JS_SetElement(cx, replays, i++, replayData); | JS_SetElement(cx, replays, i++, replayData); | ||||
newReplays = true; | |||||
} | } | ||||
return JS::ObjectValue(*replays); | else | ||||
copyFromOldCache.push_back(it->second.first); | |||||
} | |||||
} | |||||
debug_printf("old not deleted replays: %zu \n", copyFromOldCache.size()); | |||||
debug_printf("old deleted replays:: %zu \n", fileList.size() - copyFromOldCache.size()); | |||||
debug_printf("new replays: %d \n", i); | |||||
Done Inline Actionselexis: `zu` is both the [[http://www.cplusplus.com/reference/cstdio/printf/ | correcŧ]] entry for a… | |||||
Done Inline ActionsThree lines feel a bit noisy, why not all in one: debug_printf( "Loading %zu cached replays, removed %zu outdated entries, loaded %i new entries\n", fileList.size(), fileList.size() - copyFromOldCache.size(), i); elexis: Three lines feel a bit noisy, why not all in one:
```
debug_printf(
"Loading %zu cached… | |||||
Not Done Inline ActionsThis C99 format specifier %zu seems to be what causes the crash on windows (it's not used anywhere else in the code base), likely because of the limited C99 support in VS2013. Sandarac: This C99 format specifier `%zu` seems to be what causes the crash on windows (it's not used… | |||||
Not Done Inline ActionsThanks for figuring that out! (03:13:03) Sandarac: https://blogs.msdn.microsoft.com/vcblog/2013/07/19/c99-library-support-in-visual-studio-2013/ elexis: Thanks for figuring that out!
(03:13:03) Sandarac: https://blogs.msdn.microsoft. | |||||
if (!newReplays && fileList.empty()) | |||||
Not Done Inline Actions(unsigned long)fileList.size() elexis: `(unsigned long)fileList.size()` | |||||
return replays; | |||||
// No replay was changed, so just return the cache | |||||
Done Inline Actions\n after return elexis: \n after return | |||||
if (!newReplays && fileList.size() == copyFromOldCache.size()) | |||||
return cachedReplaysObject; | |||||
{ | |||||
TIMER(L"ReloadReplayCache: copy from old cache"); | |||||
// Copy the replays from the old cache that are not deleted | |||||
Done Inline Actionsnot elexis: not | |||||
if (!copyFromOldCache.empty()) | |||||
for (u32 j : copyFromOldCache) | |||||
{ | |||||
JS::RootedValue replay(cx); | |||||
JS_GetElement(cx, cachedReplaysObject, j, &replay); | |||||
JS_SetElement(cx, replays, i++, replay); | |||||
} | |||||
} | |||||
StoreCacheFile(scriptInterface, replays); | |||||
return replays; | |||||
} | |||||
JS::Value VisualReplay::GetReplays(ScriptInterface& scriptInterface, bool reload) | |||||
{ | |||||
TIMER(L"GetReplays"); | |||||
JSContext* cx = scriptInterface.GetContext(); | |||||
JSAutoRequest rq(cx); | |||||
// Acquire replays | |||||
JS::RootedObject replays(cx, ReloadReplayCache(scriptInterface, reload)); | |||||
Done Inline Actionsunneeded comment elexis: unneeded comment | |||||
// Remove entries without data | |||||
JS::RootedObject replaysWithoutNullEntries(cx, JS_NewArrayObject(cx, 0)); | |||||
Done Inline ActionsHow about not adding files without attribs instead of removing them? elexis: How about not adding files without `attribs` instead of removing them? | |||||
u32 replaysLength = 0; | |||||
Done Inline ActionsreplayNonEmpty? elexis: replayNonEmpty? | |||||
JS_GetArrayLength(cx, replays, &replaysLength); | |||||
for (u32 j = 0, i = 0; j < replaysLength; ++j) | |||||
{ | |||||
JS::RootedValue replay(cx); | |||||
JS_GetElement(cx, replays, j, &replay); | |||||
if (scriptInterface.HasProperty(replay, "attribs")) | |||||
JS_SetElement(cx, replaysWithoutNullEntries, i++, replay); | |||||
} | |||||
return JS::ObjectValue(*replaysWithoutNullEntries); | |||||
} | } | ||||
/** | /** | ||||
* Move the cursor backwards until a newline was read or the beginning of the file was found. | * Move the cursor backwards until a newline was read or the beginning of the file was found. | ||||
* Either way the cursor points to the beginning of a newline. | * Either way the cursor points to the beginning of a newline. | ||||
* | * | ||||
* @return The current cursor position or -1 on error. | * @return The current cursor position or -1 on error. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Lines | for (int linesRead = 1; linesRead < 1000; ++linesRead) | ||||
// Otherwise move cursor back to the character before the last newline | // Otherwise move cursor back to the character before the last newline | ||||
replayStream->seekg(currentPosition - 2, std::ios_base::beg); | replayStream->seekg(currentPosition - 2, std::ios_base::beg); | ||||
} | } | ||||
LOGERROR("Infinite loop when determining replay duration for %s", fileName.c_str()); | LOGERROR("Infinite loop when determining replay duration for %s", fileName.c_str()); | ||||
return -1; | return -1; | ||||
} | } | ||||
JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, OsPath& directory) | JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory) | ||||
{ | { | ||||
TIMER(L"LoadReplayData"); | |||||
Done Inline ActionsRemove this TIMER call, since we want only one call for the loading of all replay files, not one for each IMO elexis: Remove this TIMER call, since we want only one call for the loading of all replay files, not… | |||||
// The directory argument must not be constant, otherwise concatenating will fail | // The directory argument must not be constant, otherwise concatenating will fail | ||||
const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt"; | const OsPath replayFile = GetDirectoryName() / directory / L"commands.txt"; | ||||
if (!FileExists(replayFile)) | if (!FileExists(replayFile)) | ||||
return JSVAL_NULL; | return JSVAL_NULL; | ||||
// Get file size and modification date | // Get file size and modification date | ||||
CFileInfo fileInfo; | CFileInfo fileInfo; | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | JS::Value VisualReplay::LoadReplayData(ScriptInterface& scriptInterface, const OsPath& directory) | ||||
if (duration < minimumReplayDuration) | if (duration < minimumReplayDuration) | ||||
return JSVAL_NULL; | return JSVAL_NULL; | ||||
// Return the actual data | // Return the actual data | ||||
JS::RootedValue replayData(cx); | JS::RootedValue replayData(cx); | ||||
scriptInterface.Eval("({})", &replayData); | scriptInterface.Eval("({})", &replayData); | ||||
scriptInterface.SetProperty(replayData, "file", replayFile); | scriptInterface.SetProperty(replayData, "file", replayFile); | ||||
scriptInterface.SetProperty(replayData, "directory", directory); | scriptInterface.SetProperty(replayData, "directory", directory); | ||||
scriptInterface.SetProperty(replayData, "fileSize", (u32)fileSize); | |||||
Done Inline ActionsUse (double), the whole JS number type parts takes place at D205 elexis: Use (double), the whole JS number type parts takes place at D205 | |||||
scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime)); | scriptInterface.SetProperty(replayData, "filemod_timestamp", std::to_string(fileTime)); | ||||
scriptInterface.SetProperty(replayData, "attribs", attribs); | scriptInterface.SetProperty(replayData, "attribs", attribs); | ||||
scriptInterface.SetProperty(replayData, "duration", duration); | scriptInterface.SetProperty(replayData, "duration", duration); | ||||
return replayData; | return replayData; | ||||
} | } | ||||
bool VisualReplay::DeleteReplay(const CStrW& replayDirectory) | bool VisualReplay::DeleteReplay(const CStrW& replayDirectory) | ||||
{ | { | ||||
if (replayDirectory.empty()) | if (replayDirectory.empty()) | ||||
return false; | return false; | ||||
const OsPath directory = GetDirectoryName() / replayDirectory; | const OsPath directory = GetDirectoryName() / replayDirectory; | ||||
return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK; | return DirectoryExists(directory) && DeleteDirectory(directory) == INFO::OK; | ||||
} | } | ||||
JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) | JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) | ||||
{ | { | ||||
// Create empty JS object | // Create empty JS object | ||||
JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); | JSContext* cx = pCxPrivate->pScriptInterface->GetContext(); | ||||
JSAutoRequest rq(cx); | JSAutoRequest rq(cx); | ||||
JS::RootedValue attribs(cx); | JS::RootedValue attribs(cx); | ||||
pCxPrivate->pScriptInterface->Eval("({})", &attribs); | pCxPrivate->pScriptInterface->Eval("({})", &attribs); | ||||
Show All 9 Lines | JS::Value VisualReplay::GetReplayAttributes(ScriptInterface::CxPrivate* pCxPrivate, const CStrW& directoryName) | ||||
// Read and return first line | // Read and return first line | ||||
std::getline(*replayStream, line); | std::getline(*replayStream, line); | ||||
pCxPrivate->pScriptInterface->ParseJSON(line, &attribs); | pCxPrivate->pScriptInterface->ParseJSON(line, &attribs); | ||||
SAFE_DELETE(replayStream);; | SAFE_DELETE(replayStream);; | ||||
return attribs; | return attribs; | ||||
} | } | ||||
void VisualReplay::AddReplayToCache(ScriptInterface& scriptInterface, const CStrW& directoryName) | |||||
{ | |||||
TIMER(L"AddReplayToCache"); | |||||
Done Inline ActionsSince this is only called when ending the game, maybe. elexis: Since this is only called when ending the game, maybe. | |||||
JSContext* cx = scriptInterface.GetContext(); | |||||
JSAutoRequest rq(cx); | |||||
JS::RootedValue replayData(cx, LoadReplayData(scriptInterface, OsPath(directoryName))); | |||||
if (replayData.isNull()) | |||||
return; | |||||
JS::RootedObject cachedReplaysObject(cx); | |||||
if (!ReadCacheFile(scriptInterface, &cachedReplaysObject)) | |||||
cachedReplaysObject = JS_NewArrayObject(cx, 0); | |||||
u32 cacheLength = 0; | |||||
JS_GetArrayLength(cx, cachedReplaysObject, &cacheLength); | |||||
JS_SetElement(cx, cachedReplaysObject, cacheLength, replayData); | |||||
StoreCacheFile(scriptInterface, cachedReplaysObject); | |||||
} | |||||
void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface) | void VisualReplay::SaveReplayMetadata(ScriptInterface* scriptInterface) | ||||
{ | { | ||||
JSContext* cx = scriptInterface->GetContext(); | JSContext* cx = scriptInterface->GetContext(); | ||||
JSAutoRequest rq(cx); | JSAutoRequest rq(cx); | ||||
JS::RootedValue metadata(cx); | JS::RootedValue metadata(cx); | ||||
JS::RootedValue global(cx, scriptInterface->GetGlobalObject()); | JS::RootedValue global(cx, scriptInterface->GetGlobalObject()); | ||||
▲ Show 20 Lines • Show All 48 Lines • Show Last 20 Lines |
this file needs rebase following https://code.wildfiregames.com/rP19200#5c8659fc
(So reverting that commit and then rebasing the patch is actually easier)