VmaReplay: added --Lines command line argument to limit playback to only range of file lines.

Created template class RangeSequence. Moved struct StrRange, class LineSplit, class CsvSplit and related functions to Common.*.
This commit is contained in:
Adam Sawicki 2018-08-21 13:19:27 +02:00
parent 2556b377f7
commit 069dc11452
3 changed files with 325 additions and 161 deletions

View File

@ -1,5 +1,47 @@
#include "Common.h"
////////////////////////////////////////////////////////////////////////////////
// LineSplit class
bool LineSplit::GetNextLine(StrRange& out)
{
if(m_NextLineBeg < m_NumBytes)
{
out.beg = m_Data + m_NextLineBeg;
size_t currLineEnd = m_NextLineBeg;
while(currLineEnd < m_NumBytes && m_Data[currLineEnd] != '\n')
++currLineEnd;
out.end = m_Data + currLineEnd;
m_NextLineBeg = currLineEnd + 1; // Past '\n'
++m_NextLineIndex;
return true;
}
else
return false;
}
////////////////////////////////////////////////////////////////////////////////
// CsvSplit class
void CsvSplit::Set(const StrRange& line, size_t maxCount)
{
assert(maxCount <= RANGE_COUNT_MAX);
m_Line = line;
const size_t strLen = line.length();
size_t rangeIndex = 0;
size_t charIndex = 0;
while(charIndex < strLen && rangeIndex < maxCount)
{
m_Ranges[rangeIndex * 2] = charIndex;
while(charIndex < strLen && (rangeIndex + 1 == maxCount || m_Line.beg[charIndex] != ','))
++charIndex;
m_Ranges[rangeIndex * 2 + 1] = charIndex;
++rangeIndex;
++charIndex; // Past ','
}
m_Count = rangeIndex;
}
////////////////////////////////////////////////////////////////////////////////
// class CmdLineParser

View File

@ -13,6 +13,7 @@
#include <utility>
#include <chrono>
#include <string>
#include <limits>
#include <cassert>
#include <cstdlib>
@ -30,17 +31,131 @@ inline float ToFloatSeconds(duration d)
void SecondsToFriendlyStr(float seconds, std::string& out);
template <typename T>
inline T ceil_div(T x, T y)
T ceil_div(T x, T y)
{
return (x+y-1) / y;
}
template <typename T>
static inline T align_up(T val, T align)
inline T align_up(T val, T align)
{
return (val + align - 1) / align * align;
}
struct StrRange
{
const char* beg;
const char* end;
StrRange() { }
StrRange(const char* beg, const char* end) : beg(beg), end(end) { }
explicit StrRange(const char* sz) : beg(sz), end(sz + strlen(sz)) { }
explicit StrRange(const std::string& s) : beg(s.data()), end(s.data() + s.length()) { }
size_t length() const { return end - beg; }
void to_str(std::string& out) { out.assign(beg, end); }
};
inline bool StrRangeEq(const StrRange& lhs, const char* rhsSz)
{
const size_t rhsLen = strlen(rhsSz);
return rhsLen == lhs.length() &&
memcmp(lhs.beg, rhsSz, rhsLen) == 0;
}
inline bool StrRangeToUint(const StrRange& s, uint32_t& out)
{
char* end = (char*)s.end;
out = (uint32_t)strtoul(s.beg, &end, 10);
return end == s.end;
}
inline bool StrRangeToUint(const StrRange& s, uint64_t& out)
{
char* end = (char*)s.end;
out = (uint64_t)strtoull(s.beg, &end, 10);
return end == s.end;
}
inline bool StrRangeToPtr(const StrRange& s, uint64_t& out)
{
char* end = (char*)s.end;
out = (uint64_t)strtoull(s.beg, &end, 16);
return end == s.end;
}
inline bool StrRangeToFloat(const StrRange& s, float& out)
{
char* end = (char*)s.end;
out = strtof(s.beg, &end);
return end == s.end;
}
inline bool StrRangeToBool(const StrRange& s, bool& out)
{
if(s.end - s.beg == 1)
{
if(*s.beg == '1')
{
out = true;
}
else if(*s.beg == '0')
{
out = false;
}
else
{
return false;
}
}
else
{
return false;
}
return true;
}
class LineSplit
{
public:
LineSplit(const char* data, size_t numBytes) :
m_Data(data),
m_NumBytes(numBytes),
m_NextLineBeg(0),
m_NextLineIndex(0)
{
}
bool GetNextLine(StrRange& out);
size_t GetNextLineIndex() const { return m_NextLineIndex; }
private:
const char* const m_Data;
const size_t m_NumBytes;
size_t m_NextLineBeg;
size_t m_NextLineIndex;
};
class CsvSplit
{
public:
static const size_t RANGE_COUNT_MAX = 32;
void Set(const StrRange& line, size_t maxCount = RANGE_COUNT_MAX);
const StrRange& GetLine() const { return m_Line; }
size_t GetCount() const { return m_Count; }
StrRange GetRange(size_t index) const
{
return StrRange {
m_Line.beg + m_Ranges[index * 2],
m_Line.beg + m_Ranges[index * 2 + 1] };
}
private:
StrRange m_Line = { nullptr, nullptr };
size_t m_Count = 0;
size_t m_Ranges[RANGE_COUNT_MAX * 2]; // Pairs of begin-end.
};
class CmdLineParser
{
public:
@ -102,6 +217,134 @@ private:
std::string m_LastParameter;
};
/*
Parses and stores a sequence of ranges.
Upper range is inclusive.
Examples:
"1" -> [ {1, 1} ]
"1,10" -> [ {1, 1}, {10, 10} ]
"2-6" -> [ {2, 6} ]
"-8" -> [ {MIN, 8} ]
"12-" -> [ {12, MAX} ]
"1-10,12,15-" -> [ {1, 10}, {12, 12}, {15, MAX} ]
TODO: Optimize it: Do sorting and merging while parsing. Do binary search while
reading.
*/
template<typename T>
class RangeSequence
{
public:
typedef std::pair<T, T> RangeType;
void Clear() { m_Ranges.clear(); }
bool Parse(const StrRange& str);
bool IsEmpty() const { return m_Ranges.empty(); }
size_t GetCount() const { return m_Ranges.size(); }
const RangeType* GetRanges() const { return m_Ranges.data(); }
bool Includes(T number) const;
private:
std::vector<RangeType> m_Ranges;
};
template<typename T>
bool RangeSequence<T>::Parse(const StrRange& str)
{
m_Ranges.clear();
StrRange currRange = { str.beg, str.beg };
while(currRange.beg < str.end)
{
currRange.end = currRange.beg + 1;
// Find next ',' or the end.
while(currRange.end < str.end && *currRange.end != ',')
{
++currRange.end;
}
// Find '-' within this range.
const char* hyphenPos = currRange.beg;
while(hyphenPos < currRange.end && *hyphenPos != '-')
{
++hyphenPos;
}
// No hyphen - single number like '10'.
if(hyphenPos == currRange.end)
{
RangeType range;
if(!StrRangeToUint(currRange, range.first))
{
return false;
}
range.second = range.first;
m_Ranges.push_back(range);
}
// Hyphen at the end, like '10-'.
else if(hyphenPos + 1 == currRange.end)
{
const StrRange numberRange = { currRange.beg, hyphenPos };
RangeType range;
if(!StrRangeToUint(numberRange, range.first))
{
return false;
}
range.second = std::numeric_limits<T>::max();
m_Ranges.push_back(range);
}
// Hyphen at the beginning, like "-10".
else if(hyphenPos == currRange.beg)
{
const StrRange numberRange = { currRange.beg + 1, currRange.end };
RangeType range;
range.first = std::numeric_limits<T>::min();
if(!StrRangeToUint(numberRange, range.second))
{
return false;
}
m_Ranges.push_back(range);
}
// Hyphen in the middle, like "1-10".
else
{
const StrRange numberRange1 = { currRange.beg, hyphenPos };
const StrRange numberRange2 = { hyphenPos + 1, currRange.end };
RangeType range;
if(!StrRangeToUint(numberRange1, range.first) ||
!StrRangeToUint(numberRange2, range.second) ||
range.second < range.first)
{
return false;
}
m_Ranges.push_back(range);
}
// Skip ','
currRange.beg = currRange.end + 1;
}
return true;
}
template<typename T>
bool RangeSequence<T>::Includes(T number) const
{
for(const auto& it : m_Ranges)
{
if(number >= it.first && number <= it.second)
{
return true;
}
}
return false;
}
/*
class RandomNumberGenerator
{

View File

@ -33,6 +33,7 @@ static const int RESULT_ERROR_VULKAN = -4;
enum CMD_LINE_OPT
{
CMD_LINE_OPT_VERBOSITY,
CMD_LINE_OPT_LINES,
};
static enum class VERBOSITY
@ -97,6 +98,8 @@ static std::string g_FilePath;
// Most significant 16 bits are major version, least significant 16 bits are minor version.
static uint32_t g_FileVersion;
static RangeSequence<size_t> g_LineRanges;
static bool ValidateFileVersion()
{
const uint32_t major = g_FileVersion >> 16;
@ -104,163 +107,6 @@ static bool ValidateFileVersion()
return major == 1 && minor <= 2;
}
struct StrRange
{
const char* beg;
const char* end;
StrRange() { }
StrRange(const char* beg, const char* end) : beg(beg), end(end) { }
explicit StrRange(const char* sz) : beg(sz), end(sz + strlen(sz)) { }
explicit StrRange(const std::string& s) : beg(s.data()), end(s.data() + s.length()) { }
size_t length() const { return end - beg; }
void to_str(std::string& out) { out.assign(beg, end); }
};
static inline bool StrRangeEq(const StrRange& lhs, const char* rhsSz)
{
const size_t rhsLen = strlen(rhsSz);
return rhsLen == lhs.length() &&
memcmp(lhs.beg, rhsSz, rhsLen) == 0;
}
static inline bool StrRangeToUint(const StrRange& s, uint32_t& out)
{
char* end = (char*)s.end;
out = (uint32_t)strtoul(s.beg, &end, 10);
return end == s.end;
}
static inline bool StrRangeToUint(const StrRange& s, uint64_t& out)
{
char* end = (char*)s.end;
out = (uint64_t)strtoull(s.beg, &end, 10);
return end == s.end;
}
static inline bool StrRangeToPtr(const StrRange& s, uint64_t& out)
{
char* end = (char*)s.end;
out = (uint64_t)strtoull(s.beg, &end, 16);
return end == s.end;
}
static inline bool StrRangeToFloat(const StrRange& s, float& out)
{
char* end = (char*)s.end;
out = strtof(s.beg, &end);
return end == s.end;
}
static inline bool StrRangeToBool(const StrRange& s, bool& out)
{
if(s.end - s.beg == 1)
{
if(*s.beg == '1')
{
out = true;
}
else if(*s.beg == '0')
{
out = false;
}
else
{
return false;
}
}
else
{
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// LineSplit class
class LineSplit
{
public:
LineSplit(const char* data, size_t numBytes) :
m_Data(data),
m_NumBytes(numBytes),
m_NextLineBeg(0),
m_NextLineIndex(0)
{
}
bool GetNextLine(StrRange& out);
size_t GetNextLineIndex() const { return m_NextLineIndex; }
private:
const char* const m_Data;
const size_t m_NumBytes;
size_t m_NextLineBeg;
size_t m_NextLineIndex;
};
bool LineSplit::GetNextLine(StrRange& out)
{
if(m_NextLineBeg < m_NumBytes)
{
out.beg = m_Data + m_NextLineBeg;
size_t currLineEnd = m_NextLineBeg;
while(currLineEnd < m_NumBytes && m_Data[currLineEnd] != '\n')
++currLineEnd;
out.end = m_Data + currLineEnd;
m_NextLineBeg = currLineEnd + 1; // Past '\n'
++m_NextLineIndex;
return true;
}
else
return false;
}
////////////////////////////////////////////////////////////////////////////////
// CsvSplit class
class CsvSplit
{
public:
static const size_t RANGE_COUNT_MAX = 32;
void Set(const StrRange& line, size_t maxCount = RANGE_COUNT_MAX);
const StrRange& GetLine() const { return m_Line; }
size_t GetCount() const { return m_Count; }
StrRange GetRange(size_t index) const
{
return StrRange {
m_Line.beg + m_Ranges[index * 2],
m_Line.beg + m_Ranges[index * 2 + 1] };
}
private:
StrRange m_Line = { nullptr, nullptr };
size_t m_Count = 0;
size_t m_Ranges[RANGE_COUNT_MAX * 2]; // Pairs of begin-end.
};
void CsvSplit::Set(const StrRange& line, size_t maxCount)
{
assert(maxCount <= RANGE_COUNT_MAX);
m_Line = line;
const size_t strLen = line.length();
size_t rangeIndex = 0;
size_t charIndex = 0;
while(charIndex < strLen && rangeIndex < maxCount)
{
m_Ranges[rangeIndex * 2] = charIndex;
while(charIndex < strLen && (rangeIndex + 1 == maxCount || m_Line.beg[charIndex] != ','))
++charIndex;
m_Ranges[rangeIndex * 2 + 1] = charIndex;
++rangeIndex;
++charIndex; // Past ','
}
m_Count = rangeIndex;
}
static bool ParseFileVersion(const StrRange& s)
{
CsvSplit csvSplit;
@ -2042,11 +1888,15 @@ static void PrintCommandLineSyntax()
" 0 - Minimum verbosity. Prints only warnings and errors.\n"
" 1 - Default verbosity. Prints important messages and statistics.\n"
" 2 - Maximum verbosity. Prints a lot of information.\n"
" --Lines <Ranges> - Replay only limited set of lines from file\n"
" Ranges is comma-separated list of ranges, e.g. \"-10,15,18-25,31-\".\n"
);
}
static int ProcessFile(const char* data, size_t numBytes)
{
const bool useLineRanges = !g_LineRanges.IsEmpty();
// Begin stats.
if(g_Verbosity == VERBOSITY::MAXIMUM)
{
@ -2076,18 +1926,36 @@ static int ProcessFile(const char* data, size_t numBytes)
Player player;
int result = player.Init();
size_t executedLineCount = 0;
if(result == 0)
{
if(g_Verbosity > VERBOSITY::MINIMUM)
{
printf("Playing...\n");
if(useLineRanges)
{
printf("Playing (limited range of lines)...\n");
}
else
{
printf("Playing...\n");
}
}
const time_point timeBeg = std::chrono::high_resolution_clock::now();
while(lineSplit.GetNextLine(line))
{
player.ExecuteLine(lineSplit.GetNextLineIndex(), line);
bool execute = true;
if(useLineRanges)
{
execute = g_LineRanges.Includes(lineSplit.GetNextLineIndex());
}
if(execute)
{
player.ExecuteLine(lineSplit.GetNextLineIndex(), line);
++executedLineCount;
}
}
const duration playDuration = std::chrono::high_resolution_clock::now() - timeBeg;
@ -2099,6 +1967,7 @@ static int ProcessFile(const char* data, size_t numBytes)
SecondsToFriendlyStr(ToFloatSeconds(playDuration), playDurationStr);
printf("Done.\n");
printf("Executed %zu file lines\n", executedLineCount);
printf("Playback took: %s\n", playDurationStr.c_str());
}
if(g_Verbosity == VERBOSITY::MAXIMUM)
@ -2154,6 +2023,7 @@ static int main2(int argc, char** argv)
CmdLineParser cmdLineParser(argc, argv);
cmdLineParser.RegisterOpt(CMD_LINE_OPT_VERBOSITY, 'v', true);
cmdLineParser.RegisterOpt(CMD_LINE_OPT_LINES, "Lines", true);
CmdLineParser::RESULT res;
while((res = cmdLineParser.ReadNext()) != CmdLineParser::RESULT_END)
@ -2178,6 +2048,15 @@ static int main2(int argc, char** argv)
}
}
break;
case CMD_LINE_OPT_LINES:
{
if(!g_LineRanges.Parse(StrRange(cmdLineParser.GetParameter())))
{
PrintCommandLineSyntax();
return RESULT_ERROR_COMMAND_LINE;
}
}
break;
default:
assert(0);
}