Changeset View
Changeset View
Standalone View
Standalone View
source/ps/VisualReplay.cpp
Context not available. | |||||
* | * | ||||
* @return The current cursor position or -1 on error. | * @return The current cursor position or -1 on error. | ||||
*/ | */ | ||||
inline off_t goBackToLineBeginning(std::istream* replayStream, const OsPath& fileName, off_t fileSize) | inline off_t goBackToLineBeginning(std::istream* replayStream, const std::string& identifier, off_t fileSize) | ||||
{ | { | ||||
int currentPos; | int currentPos; | ||||
char character; | char character; | ||||
Context not available. | |||||
if (!replayStream->good()) | if (!replayStream->good()) | ||||
{ | { | ||||
LOGERROR("Unknown error when returning to the last line (%i of %lu) of %s", currentPos, fileSize, fileName.string8().c_str()); | LOGERROR("Unknown error when returning to the last line (%i of %lu) of %s", currentPos, fileSize, identifier.c_str()); | ||||
return -1; | return -1; | ||||
} | } | ||||
Context not available. | |||||
replayStream->seekg(-2, std::ios_base::cur); | replayStream->seekg(-2, std::ios_base::cur); | ||||
} | } | ||||
LOGERROR("Infinite loop when going back to a line beginning in %s", fileName.string8().c_str()); | LOGERROR("Infinite loop when going back to a line beginning in %s", identifier.c_str()); | ||||
return -1; | return -1; | ||||
} | } | ||||
/** | int VisualReplay::GetReplayDuration(std::istream* replayStream, std::string& identifier, off_t fileSize) | ||||
* Compute game duration in seconds. Assume constant turn length. | |||||
* Find the last line that starts with "turn" by reading the file backwards. | |||||
* | |||||
* @return seconds or -1 on error | |||||
*/ | |||||
inline int getReplayDuration(std::istream* replayStream, const OsPath& fileName, off_t fileSize) | |||||
{ | { | ||||
CStr type; | CStr type; | ||||
Context not available. | |||||
// There should be about 5 lines to read until a turn is found. | // There should be about 5 lines to read until a turn is found. | ||||
for (int linesRead = 1; linesRead < 1000; ++linesRead) | for (int linesRead = 1; linesRead < 1000; ++linesRead) | ||||
{ | { | ||||
off_t currentPosition = goBackToLineBeginning(replayStream, fileName, fileSize); | off_t currentPosition = goBackToLineBeginning(replayStream, identifier, fileSize); | ||||
// Read error or reached file beginning. No turns exist. | // Read error or reached file beginning. No turns exist. | ||||
if (currentPosition < 1) | if (currentPosition < 1) | ||||
Context not available. | |||||
if (!replayStream->good()) | if (!replayStream->good()) | ||||
{ | { | ||||
LOGERROR("Read error when determining replay duration at %i of %llu in %s", currentPosition - 2, fileSize, fileName.string8().c_str()); | LOGERROR("Read error when determining replay duration at %i of %llu in %s", currentPosition - 2, fileSize, identifier.c_str()); | ||||
return -1; | return -1; | ||||
} | } | ||||
Context not available. | |||||
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.string8().c_str()); | LOGERROR("Infinite loop when determining replay duration for %s", identifier.c_str()); | ||||
return -1; | return -1; | ||||
} | } | ||||
bool VisualReplay::ParseHeader(const ScriptInterface& scriptInterface, JS::MutableHandleValue attribs, std::istream* replayStream, std::string identifier, bool full) | |||||
{ | |||||
JSContext* cx = scriptInterface.GetContext(); | |||||
CStr type; | |||||
if (!(*replayStream >> type).good()) | |||||
{ | |||||
LOGERROR("Couldn't open %s.", identifier.c_str()); | |||||
return false; | |||||
} | |||||
if (type != "start") | |||||
{ | |||||
LOGWARNING("The replay %s doesn't begin with 'start'!", identifier.c_str()); | |||||
return false; | |||||
} | |||||
// Parse header / first line | |||||
CStr header; | |||||
std::getline(*replayStream, header); | |||||
JSAutoRequest rq(cx); | |||||
if (!scriptInterface.ParseJSON(header, attribs)) | |||||
{ | |||||
LOGERROR("Couldn't parse replay header of %s", identifier.c_str()); | |||||
return false; | |||||
} | |||||
if (full) | |||||
{ | |||||
// Ensure "turn" after header | |||||
if (!(*replayStream >> type).good() || type != "turn") | |||||
{ | |||||
return false; | |||||
} | |||||
// Don't process files of rejoined clients | |||||
u32 turn = 1; | |||||
*replayStream >> turn; | |||||
if (turn != 0) | |||||
{ | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
JS::Value VisualReplay::LoadReplayData(const ScriptInterface& scriptInterface, const OsPath& directory) | JS::Value VisualReplay::LoadReplayData(const ScriptInterface& scriptInterface, const OsPath& directory) | ||||
{ | { | ||||
// The directory argument must not be constant, otherwise concatenating will fail | // The directory argument must not be constant, otherwise concatenating will fail | ||||
Context not available. | |||||
std::ifstream* replayStream = new std::ifstream(OsString(replayFile).c_str()); | std::ifstream* replayStream = new std::ifstream(OsString(replayFile).c_str()); | ||||
CStr type; | std::string identifier = replayFile.string8(); | ||||
if (!(*replayStream >> type).good()) | |||||
{ | |||||
LOGERROR("Couldn't open %s.", replayFile.string8().c_str()); | |||||
SAFE_DELETE(replayStream); | |||||
return JSVAL_NULL; | |||||
} | |||||
if (type != "start") | |||||
{ | |||||
LOGWARNING("The replay %s doesn't begin with 'start'!", replayFile.string8().c_str()); | |||||
SAFE_DELETE(replayStream); | |||||
return JSVAL_NULL; | |||||
} | |||||
// Parse header / first line | |||||
CStr header; | |||||
std::getline(*replayStream, header); | |||||
JSContext* cx = scriptInterface.GetContext(); | JSContext* cx = scriptInterface.GetContext(); | ||||
JSAutoRequest rq(cx); | |||||
JS::RootedValue attribs(cx); | JS::RootedValue attribs(cx); | ||||
if (!scriptInterface.ParseJSON(header, &attribs)) | scriptInterface.Eval("({})", &attribs); | ||||
{ | if (!ParseHeader(scriptInterface, &attribs, replayStream, identifier, true) ) { | ||||
LOGERROR("Couldn't parse replay header of %s", replayFile.string8().c_str()); | |||||
SAFE_DELETE(replayStream); | SAFE_DELETE(replayStream); | ||||
return JSVAL_NULL; | return JSVAL_NULL; | ||||
} | } | ||||
// Ensure "turn" after header | int duration = GetReplayDuration(replayStream, identifier, fileSize); | ||||
if (!(*replayStream >> type).good() || type != "turn") | |||||
{ | |||||
SAFE_DELETE(replayStream); | |||||
return JSVAL_NULL; // there are no turns at all | |||||
} | |||||
// Don't process files of rejoined clients | |||||
u32 turn = 1; | |||||
*replayStream >> turn; | |||||
if (turn != 0) | |||||
{ | |||||
SAFE_DELETE(replayStream); | |||||
return JSVAL_NULL; | |||||
} | |||||
int duration = getReplayDuration(replayStream, replayFile, fileSize); | |||||
SAFE_DELETE(replayStream); | SAFE_DELETE(replayStream); | ||||
Context not available. | |||||
// Open file | // Open file | ||||
std::istream* replayStream = new std::ifstream(OsString(replayFile).c_str()); | std::istream* replayStream = new std::ifstream(OsString(replayFile).c_str()); | ||||
CStr type, line; | std::string identifier = replayFile.string8(); | ||||
ENSURE((*replayStream >> type).good() && type == "start"); | ENSURE(ParseHeader(*pCxPrivate->pScriptInterface, &attribs, replayStream, identifier, true)); | ||||
SAFE_DELETE(replayStream); | |||||
// Read and return first line | |||||
std::getline(*replayStream, line); | |||||
pCxPrivate->pScriptInterface->ParseJSON(line, &attribs); | |||||
SAFE_DELETE(replayStream);; | |||||
return attribs; | return attribs; | ||||
} | } | ||||
Context not available. |
Wildfire Games · Phabricator