From 069dc11452eb2a1ad9fa0c463621b0e2934b6cd4 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Tue, 21 Aug 2018 13:19:27 +0200 Subject: [PATCH] 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.*. --- src/VmaReplay/Common.cpp | 42 ++++++ src/VmaReplay/Common.h | 247 +++++++++++++++++++++++++++++++++++- src/VmaReplay/VmaReplay.cpp | 197 ++++++---------------------- 3 files changed, 325 insertions(+), 161 deletions(-) diff --git a/src/VmaReplay/Common.cpp b/src/VmaReplay/Common.cpp index cfb44dc..0350343 100644 --- a/src/VmaReplay/Common.cpp +++ b/src/VmaReplay/Common.cpp @@ -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 diff --git a/src/VmaReplay/Common.h b/src/VmaReplay/Common.h index f5f4c44..e4b6251 100644 --- a/src/VmaReplay/Common.h +++ b/src/VmaReplay/Common.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -30,17 +31,131 @@ inline float ToFloatSeconds(duration d) void SecondsToFriendlyStr(float seconds, std::string& out); template -inline T ceil_div(T x, T y) +T ceil_div(T x, T y) { return (x+y-1) / y; } template -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 +class RangeSequence +{ +public: + typedef std::pair 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 m_Ranges; +}; + +template +bool RangeSequence::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::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::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 +bool RangeSequence::Includes(T number) const +{ + for(const auto& it : m_Ranges) + { + if(number >= it.first && number <= it.second) + { + return true; + } + } + return false; +} + /* class RandomNumberGenerator { diff --git a/src/VmaReplay/VmaReplay.cpp b/src/VmaReplay/VmaReplay.cpp index 58cd279..dba1005 100644 --- a/src/VmaReplay/VmaReplay.cpp +++ b/src/VmaReplay/VmaReplay.cpp @@ -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 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 - 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); }