1148 lines
26 KiB
C++
1148 lines
26 KiB
C++
#include "AppConfig.hpp"
|
|
|
|
#include <cctype>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
std::optional<PresentModeConfig> parsePresentMode(
|
|
std::string_view text);
|
|
|
|
namespace
|
|
{
|
|
struct JsonValue
|
|
{
|
|
using Object = std::map<std::string, JsonValue>;
|
|
using Array = std::vector<JsonValue>;
|
|
|
|
std::variant<
|
|
std::nullptr_t,
|
|
bool,
|
|
double,
|
|
std::string,
|
|
Object,
|
|
Array> value;
|
|
};
|
|
|
|
class JsonParser
|
|
{
|
|
public:
|
|
explicit JsonParser(std::string_view text)
|
|
: mText(text)
|
|
{
|
|
}
|
|
|
|
std::optional<JsonValue> parse()
|
|
{
|
|
JsonValue result;
|
|
if (!parseValue(result))
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
skipWhitespace();
|
|
if (mPos != mText.size())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
void skipWhitespace()
|
|
{
|
|
while (mPos < mText.size() &&
|
|
std::isspace(
|
|
static_cast<unsigned char>(mText[mPos])))
|
|
{
|
|
++mPos;
|
|
}
|
|
}
|
|
|
|
bool consume(char expected)
|
|
{
|
|
skipWhitespace();
|
|
if (mPos >= mText.size() ||
|
|
mText[mPos] != expected)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
++mPos;
|
|
return true;
|
|
}
|
|
|
|
bool parseValue(JsonValue& out)
|
|
{
|
|
skipWhitespace();
|
|
if (mPos >= mText.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const char c = mText[mPos];
|
|
if (c == '{')
|
|
{
|
|
return parseObject(out);
|
|
}
|
|
if (c == '[')
|
|
{
|
|
return parseArray(out);
|
|
}
|
|
if (c == '"')
|
|
{
|
|
std::string s;
|
|
if (!parseString(s))
|
|
{
|
|
return false;
|
|
}
|
|
out.value = std::move(s);
|
|
return true;
|
|
}
|
|
if (c == '-' || (c >= '0' && c <= '9'))
|
|
{
|
|
return parseNumber(out);
|
|
}
|
|
if (mText.substr(mPos, 4) == "true")
|
|
{
|
|
mPos += 4;
|
|
out.value = true;
|
|
return true;
|
|
}
|
|
if (mText.substr(mPos, 5) == "false")
|
|
{
|
|
mPos += 5;
|
|
out.value = false;
|
|
return true;
|
|
}
|
|
if (mText.substr(mPos, 4) == "null")
|
|
{
|
|
mPos += 4;
|
|
out.value = nullptr;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool parseObject(JsonValue& out)
|
|
{
|
|
if (!consume('{'))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
JsonValue::Object object;
|
|
skipWhitespace();
|
|
if (mPos < mText.size() && mText[mPos] == '}')
|
|
{
|
|
++mPos;
|
|
out.value = std::move(object);
|
|
return true;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
std::string key;
|
|
if (!parseString(key) || !consume(':'))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
JsonValue value;
|
|
if (!parseValue(value))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
object[std::move(key)] = std::move(value);
|
|
|
|
skipWhitespace();
|
|
if (mPos < mText.size() && mText[mPos] == '}')
|
|
{
|
|
++mPos;
|
|
out.value = std::move(object);
|
|
return true;
|
|
}
|
|
if (!consume(','))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool parseArray(JsonValue& out)
|
|
{
|
|
if (!consume('['))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
JsonValue::Array array;
|
|
skipWhitespace();
|
|
if (mPos < mText.size() && mText[mPos] == ']')
|
|
{
|
|
++mPos;
|
|
out.value = std::move(array);
|
|
return true;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
JsonValue value;
|
|
if (!parseValue(value))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
array.push_back(std::move(value));
|
|
|
|
skipWhitespace();
|
|
if (mPos < mText.size() && mText[mPos] == ']')
|
|
{
|
|
++mPos;
|
|
out.value = std::move(array);
|
|
return true;
|
|
}
|
|
if (!consume(','))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool parseString(std::string& out)
|
|
{
|
|
skipWhitespace();
|
|
if (mPos >= mText.size() || mText[mPos] != '"')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
++mPos;
|
|
out.clear();
|
|
|
|
while (mPos < mText.size())
|
|
{
|
|
const char c = mText[mPos++];
|
|
if (c == '"')
|
|
{
|
|
return true;
|
|
}
|
|
if (c != '\\')
|
|
{
|
|
out.push_back(c);
|
|
continue;
|
|
}
|
|
if (mPos >= mText.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const char esc = mText[mPos++];
|
|
switch (esc)
|
|
{
|
|
case '"': out.push_back('"'); break;
|
|
case '\\': out.push_back('\\'); break;
|
|
case '/': out.push_back('/'); break;
|
|
case 'b': out.push_back('\b'); break;
|
|
case 'f': out.push_back('\f'); break;
|
|
case 'n': out.push_back('\n'); break;
|
|
case 'r': out.push_back('\r'); break;
|
|
case 't': out.push_back('\t'); break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool parseNumber(JsonValue& out)
|
|
{
|
|
skipWhitespace();
|
|
const size_t start = mPos;
|
|
|
|
if (mPos < mText.size() && mText[mPos] == '-')
|
|
{
|
|
++mPos;
|
|
}
|
|
while (mPos < mText.size() &&
|
|
mText[mPos] >= '0' && mText[mPos] <= '9')
|
|
{
|
|
++mPos;
|
|
}
|
|
if (mPos < mText.size() && mText[mPos] == '.')
|
|
{
|
|
++mPos;
|
|
while (mPos < mText.size() &&
|
|
mText[mPos] >= '0' && mText[mPos] <= '9')
|
|
{
|
|
++mPos;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
out.value = std::stod(
|
|
std::string(mText.substr(start, mPos - start))
|
|
);
|
|
}
|
|
catch (...)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string_view mText;
|
|
size_t mPos = 0;
|
|
};
|
|
|
|
bool parseUint32(
|
|
const char* text,
|
|
uint32_t& value)
|
|
{
|
|
uint32_t parsed = 0;
|
|
bool hasDigit = false;
|
|
|
|
for (int i = 0; text[i] != '\0'; ++i)
|
|
{
|
|
if (text[i] < '0' || text[i] > '9')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
hasDigit = true;
|
|
parsed = parsed * 10u +
|
|
static_cast<uint32_t>(text[i] - '0');
|
|
}
|
|
|
|
if (!hasDigit)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
value = parsed;
|
|
return true;
|
|
}
|
|
|
|
bool parseFeedIndex(
|
|
const char* text,
|
|
uint32_t maxFeeds,
|
|
uint32_t& index)
|
|
{
|
|
uint32_t parsed = 0;
|
|
|
|
if (!parseUint32(text, parsed) ||
|
|
parsed >= maxFeeds)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
index = parsed;
|
|
return true;
|
|
}
|
|
|
|
bool parseGrid(
|
|
std::string_view text,
|
|
uint32_t& cols,
|
|
uint32_t& rows)
|
|
{
|
|
const size_t xPos = text.find('x');
|
|
if (xPos == std::string_view::npos)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const std::string colText(text.substr(0, xPos));
|
|
const std::string rowText(text.substr(xPos + 1));
|
|
|
|
uint32_t parsedCols = 0;
|
|
uint32_t parsedRows = 0;
|
|
|
|
if (!parseUint32(colText.c_str(), parsedCols) ||
|
|
!parseUint32(rowText.c_str(), parsedRows) ||
|
|
parsedCols == 0 ||
|
|
parsedRows == 0 ||
|
|
parsedCols > MaxFeedCount / parsedRows)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
cols = parsedCols;
|
|
rows = parsedRows;
|
|
return true;
|
|
}
|
|
|
|
uint32_t feedCount(const AppConfig& config)
|
|
{
|
|
return config.gridCols * config.gridRows;
|
|
}
|
|
|
|
void applyDefaultFeeds(AppConfig& config)
|
|
{
|
|
config.feeds.assign(feedCount(config), FeedConfig{});
|
|
}
|
|
|
|
void resizeFeedsForGrid(AppConfig& config)
|
|
{
|
|
config.feeds.resize(feedCount(config));
|
|
}
|
|
|
|
const JsonValue* objectField(
|
|
const JsonValue::Object& object,
|
|
const char* key)
|
|
{
|
|
const auto it = object.find(key);
|
|
if (it == object.end())
|
|
{
|
|
return nullptr;
|
|
}
|
|
return &it->second;
|
|
}
|
|
|
|
const std::string* jsonString(const JsonValue* value)
|
|
{
|
|
if (value == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return std::get_if<std::string>(&value->value);
|
|
}
|
|
|
|
const JsonValue::Array* jsonArray(const JsonValue* value)
|
|
{
|
|
if (value == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return std::get_if<JsonValue::Array>(&value->value);
|
|
}
|
|
|
|
bool jsonBool(const JsonValue* value, bool& out)
|
|
{
|
|
if (value == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const bool* parsed = std::get_if<bool>(&value->value);
|
|
if (parsed == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
out = *parsed;
|
|
return true;
|
|
}
|
|
|
|
bool jsonUint32(const JsonValue* value, uint32_t& out)
|
|
{
|
|
if (value == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const double* parsed = std::get_if<double>(&value->value);
|
|
if (parsed == nullptr ||
|
|
*parsed < 0.0 ||
|
|
*parsed > 4294967295.0 ||
|
|
*parsed != static_cast<uint32_t>(*parsed))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
out = static_cast<uint32_t>(*parsed);
|
|
return true;
|
|
}
|
|
|
|
bool applyConfigFile(
|
|
AppConfig& config,
|
|
const char* path,
|
|
std::string& error)
|
|
{
|
|
std::ifstream file(path);
|
|
if (!file)
|
|
{
|
|
error = "failed to open config file";
|
|
return false;
|
|
}
|
|
|
|
const std::string text(
|
|
(std::istreambuf_iterator<char>(file)),
|
|
std::istreambuf_iterator<char>());
|
|
|
|
std::optional<JsonValue> rootValue =
|
|
JsonParser(text).parse();
|
|
if (!rootValue.has_value())
|
|
{
|
|
error = "invalid JSON";
|
|
return false;
|
|
}
|
|
|
|
const auto* root =
|
|
std::get_if<JsonValue::Object>(&rootValue->value);
|
|
if (root == nullptr)
|
|
{
|
|
error = "config root must be an object";
|
|
return false;
|
|
}
|
|
|
|
if (const std::string* grid =
|
|
jsonString(objectField(*root, "grid")))
|
|
{
|
|
if (!parseGrid(
|
|
*grid,
|
|
config.gridCols,
|
|
config.gridRows))
|
|
{
|
|
error = "grid must be <cols>x<rows> with 1-16 total feeds";
|
|
return false;
|
|
}
|
|
resizeFeedsForGrid(config);
|
|
}
|
|
|
|
uint32_t cols = 0;
|
|
uint32_t rows = 0;
|
|
const bool hasCols =
|
|
jsonUint32(objectField(*root, "cols"), cols);
|
|
const bool hasRows =
|
|
jsonUint32(objectField(*root, "rows"), rows);
|
|
if (hasCols || hasRows)
|
|
{
|
|
if (!hasCols || !hasRows ||
|
|
cols == 0 || rows == 0 ||
|
|
cols > MaxFeedCount / rows)
|
|
{
|
|
error = "cols and rows must describe 1-16 total feeds";
|
|
return false;
|
|
}
|
|
|
|
config.gridCols = cols;
|
|
config.gridRows = rows;
|
|
resizeFeedsForGrid(config);
|
|
}
|
|
|
|
uint32_t fpsCap = 0;
|
|
if (jsonUint32(objectField(*root, "fpsCap"), fpsCap))
|
|
{
|
|
if (fpsCap == 0 || fpsCap > 240)
|
|
{
|
|
error = "fpsCap must be 1-240";
|
|
return false;
|
|
}
|
|
config.fpsCap = fpsCap;
|
|
}
|
|
|
|
uint32_t maxV210UploadsPerFrame = 0;
|
|
if (jsonUint32(
|
|
objectField(*root, "maxV210UploadsPerFrame"),
|
|
maxV210UploadsPerFrame))
|
|
{
|
|
if (maxV210UploadsPerFrame > MaxFeedCount)
|
|
{
|
|
error = "maxV210UploadsPerFrame must be 0-16";
|
|
return false;
|
|
}
|
|
config.maxV210UploadsPerFrame = maxV210UploadsPerFrame;
|
|
}
|
|
|
|
bool boolValue = false;
|
|
if (jsonBool(objectField(*root, "noFps"), boolValue))
|
|
{
|
|
config.logFps = !boolValue;
|
|
}
|
|
if (jsonBool(objectField(*root, "perf"), boolValue))
|
|
{
|
|
config.logPerf = boolValue;
|
|
}
|
|
if (jsonBool(objectField(*root, "verbose"), boolValue))
|
|
{
|
|
config.verbose = boolValue;
|
|
}
|
|
if (jsonBool(objectField(*root, "paceUploads"), boolValue))
|
|
{
|
|
config.paceUploads = boolValue;
|
|
}
|
|
|
|
if (const std::string* present =
|
|
jsonString(objectField(*root, "present")))
|
|
{
|
|
const std::optional<PresentModeConfig> mode =
|
|
parsePresentMode(*present);
|
|
if (!mode.has_value())
|
|
{
|
|
error = "present must be fifo, mailbox, or immediate";
|
|
return false;
|
|
}
|
|
config.presentMode = mode.value();
|
|
}
|
|
|
|
const JsonValue::Array* feeds =
|
|
jsonArray(objectField(*root, "feeds"));
|
|
if (feeds == nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (feeds->size() > MaxFeedCount)
|
|
{
|
|
error = "feeds array exceeds 16 entries";
|
|
return false;
|
|
}
|
|
|
|
if (feeds->size() > config.feeds.size())
|
|
{
|
|
uint32_t side = 1;
|
|
while (side * side < feeds->size())
|
|
{
|
|
++side;
|
|
}
|
|
|
|
config.gridCols = side;
|
|
config.gridRows =
|
|
static_cast<uint32_t>(
|
|
(feeds->size() + side - 1) / side);
|
|
resizeFeedsForGrid(config);
|
|
}
|
|
|
|
for (size_t i = 0; i < feeds->size(); ++i)
|
|
{
|
|
const auto* feedObject =
|
|
std::get_if<JsonValue::Object>(
|
|
&(*feeds)[i].value);
|
|
if (feedObject == nullptr)
|
|
{
|
|
error = "each feed must be an object";
|
|
return false;
|
|
}
|
|
|
|
FeedConfig feed;
|
|
|
|
if (const std::string* kind =
|
|
jsonString(objectField(*feedObject, "kind")))
|
|
{
|
|
const std::optional<FeedKind> parsed =
|
|
parseFeedKind(*kind);
|
|
if (!parsed.has_value())
|
|
{
|
|
error = "unknown feed kind: " + *kind;
|
|
return false;
|
|
}
|
|
feed.kind = parsed.value();
|
|
}
|
|
|
|
if (const std::string* domain =
|
|
jsonString(objectField(*feedObject, "domain")))
|
|
{
|
|
feed.mxlDomain = *domain;
|
|
}
|
|
|
|
const std::string* flow =
|
|
jsonString(objectField(*feedObject, "flow"));
|
|
if (flow == nullptr)
|
|
{
|
|
flow = jsonString(objectField(*feedObject, "flowId"));
|
|
}
|
|
if (flow != nullptr)
|
|
{
|
|
feed.mxlFlowId = *flow;
|
|
feed.kind = FeedKind::MxlSdk;
|
|
}
|
|
|
|
config.feeds[i] = std::move(feed);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::optional<FeedKind> parseFeedKind(std::string_view text)
|
|
{
|
|
if (text == "nosignal" ||
|
|
text == "none" ||
|
|
text == "fake")
|
|
{
|
|
return FeedKind::NoSignal;
|
|
}
|
|
|
|
if (text == "mxl")
|
|
{
|
|
return FeedKind::MxlPlaceholder;
|
|
}
|
|
|
|
if (text == "smpte")
|
|
{
|
|
return FeedKind::SmpteBars;
|
|
}
|
|
|
|
if (text == "mxlsdk")
|
|
{
|
|
return FeedKind::MxlSdk;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<PresentModeConfig> parsePresentMode(std::string_view text)
|
|
{
|
|
if (text == "fifo")
|
|
{
|
|
return PresentModeConfig::Fifo;
|
|
}
|
|
|
|
if (text == "mailbox")
|
|
{
|
|
return PresentModeConfig::Mailbox;
|
|
}
|
|
|
|
if (text == "immediate")
|
|
{
|
|
return PresentModeConfig::Immediate;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::string_view feedKindName(FeedKind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case FeedKind::MxlPlaceholder:
|
|
return "mxl";
|
|
|
|
case FeedKind::SmpteBars:
|
|
return "smpte";
|
|
|
|
case FeedKind::MxlSdk:
|
|
return "mxlsdk";
|
|
|
|
case FeedKind::NoSignal:
|
|
default:
|
|
return "nosignal";
|
|
}
|
|
}
|
|
|
|
void printUsage(const char* executableName)
|
|
{
|
|
std::cout
|
|
<< "Usage: "
|
|
<< executableName
|
|
<< " [options] [feed...]\n"
|
|
<< "Options:\n"
|
|
<< " --no-fps Disable FPS logging\n"
|
|
<< " --perf Log per-frame CPU timing breakdown\n"
|
|
<< " --verbose Enable detailed startup and SDK logs\n"
|
|
<< " --pace-uploads Pace v210 uploads by feed frame rate\n"
|
|
<< " --config <path> Load JSON feed configuration\n"
|
|
<< " --grid <cols>x<rows> Set multiview grid (default 2x2, max 16 feeds)\n"
|
|
<< " --fps-cap <fps> Limit render loop FPS (default 60)\n"
|
|
<< " --max-v210-uploads <n> Limit v210 uploads per frame (default "
|
|
<< DefaultMaxV210UploadsPerFrame
|
|
<< ", 0 = unlimited)\n"
|
|
<< " --present <mode> Present mode: fifo, mailbox, immediate (default mailbox)\n"
|
|
<< " --feed <idx> <kind> Set feed slot kind explicitly\n"
|
|
<< " --domain <idx> <path> Set MXL domain for feed slot\n"
|
|
<< " --flow <idx> <uuid> Set MXL flow UUID for feed slot\n"
|
|
<< " --version Print version\n"
|
|
<< " --help Print this help\n"
|
|
<< "Feed kinds: nosignal, none, fake, mxl, smpte, mxlsdk\n"
|
|
<< "Default: 2x2 grid, all slots nosignal"
|
|
<< std::endl;
|
|
}
|
|
|
|
ConfigParseResult parseAppConfig(
|
|
int argc,
|
|
char* argv[])
|
|
{
|
|
ConfigParseResult result;
|
|
|
|
applyDefaultFeeds(result.config);
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
const std::string_view arg(argv[i]);
|
|
|
|
if (arg == "--help" || arg == "-h")
|
|
{
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 0;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
const std::string_view arg(argv[i]);
|
|
|
|
if (arg == "--version" || arg == "-v")
|
|
{
|
|
std::cout
|
|
<< AppName
|
|
<< " "
|
|
<< AppVersionMajor
|
|
<< "."
|
|
<< AppVersionMinor
|
|
<< "."
|
|
<< AppVersionPatch
|
|
<< std::endl;
|
|
|
|
result.shouldExit = true;
|
|
result.exitCode = 0;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
if (std::string_view(argv[i]) != "--config")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (i + 1 >= argc)
|
|
{
|
|
std::cerr << "--config requires <path>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
std::string error;
|
|
if (!applyConfigFile(
|
|
result.config,
|
|
argv[i + 1],
|
|
error))
|
|
{
|
|
std::cerr << "--config " << argv[i + 1]
|
|
<< ": " << error << std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
if (std::string_view(argv[i]) != "--grid")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (i + 1 >= argc ||
|
|
!parseGrid(
|
|
argv[i + 1],
|
|
result.config.gridCols,
|
|
result.config.gridRows))
|
|
{
|
|
std::cerr << "--grid must be <cols>x<rows> with 1-"
|
|
<< MaxFeedCount << " total feeds"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
++i;
|
|
resizeFeedsForGrid(result.config);
|
|
}
|
|
|
|
std::vector<std::string_view> feedArgs;
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
const std::string_view arg(argv[i]);
|
|
|
|
if (arg == "--no-fps")
|
|
{
|
|
result.config.logFps = false;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--perf")
|
|
{
|
|
result.config.logPerf = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--verbose")
|
|
{
|
|
result.config.verbose = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--pace-uploads")
|
|
{
|
|
result.config.paceUploads = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--grid")
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--config")
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--fps-cap")
|
|
{
|
|
if (i + 1 >= argc)
|
|
{
|
|
std::cerr << "--fps-cap requires <fps>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t fpsCap = 0;
|
|
|
|
if (!parseUint32(argv[i + 1], fpsCap) ||
|
|
fpsCap == 0 || fpsCap > 240)
|
|
{
|
|
std::cerr << "--fps-cap must be 1-240"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.fpsCap = fpsCap;
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--max-v210-uploads")
|
|
{
|
|
if (i + 1 >= argc)
|
|
{
|
|
std::cerr << "--max-v210-uploads requires <count>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t maxUploads = 0;
|
|
if (!parseUint32(argv[i + 1], maxUploads) ||
|
|
maxUploads > MaxFeedCount)
|
|
{
|
|
std::cerr << "--max-v210-uploads must be 0-"
|
|
<< MaxFeedCount << std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.maxV210UploadsPerFrame = maxUploads;
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--present")
|
|
{
|
|
if (i + 1 >= argc)
|
|
{
|
|
std::cerr << "--present requires <mode>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
const std::optional<PresentModeConfig> mode =
|
|
parsePresentMode(argv[i + 1]);
|
|
|
|
if (!mode.has_value())
|
|
{
|
|
std::cerr
|
|
<< "--present must be fifo, mailbox, or immediate"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.presentMode = mode.value();
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--feed")
|
|
{
|
|
if (i + 2 >= argc)
|
|
{
|
|
std::cerr << "--feed requires <index> <kind>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t index = 0;
|
|
if (!parseFeedIndex(
|
|
argv[i + 1],
|
|
feedCount(result.config),
|
|
index))
|
|
{
|
|
std::cerr << "--feed index must be 0-"
|
|
<< (feedCount(result.config) - 1) << std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
const std::optional<FeedKind> kind =
|
|
parseFeedKind(argv[i + 2]);
|
|
|
|
if (!kind.has_value())
|
|
{
|
|
std::cerr << "Unknown feed kind: "
|
|
<< argv[i + 2] << std::endl;
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.feeds[index].kind = kind.value();
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--domain")
|
|
{
|
|
if (i + 2 >= argc)
|
|
{
|
|
std::cerr << "--domain requires <index> <path>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t index = 0;
|
|
if (!parseFeedIndex(
|
|
argv[i + 1],
|
|
feedCount(result.config),
|
|
index))
|
|
{
|
|
std::cerr << "--domain index must be 0-"
|
|
<< (feedCount(result.config) - 1) << std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.feeds[index].mxlDomain =
|
|
std::string(argv[i + 2]);
|
|
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
if (arg == "--flow")
|
|
{
|
|
if (i + 2 >= argc)
|
|
{
|
|
std::cerr << "--flow requires <index> <uuid>"
|
|
<< std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
uint32_t index = 0;
|
|
if (!parseFeedIndex(
|
|
argv[i + 1],
|
|
feedCount(result.config),
|
|
index))
|
|
{
|
|
std::cerr << "--flow index must be 0-"
|
|
<< (feedCount(result.config) - 1) << std::endl;
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.feeds[index].mxlFlowId =
|
|
std::string(argv[i + 2]);
|
|
result.config.feeds[index].kind =
|
|
FeedKind::MxlSdk;
|
|
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
feedArgs.push_back(arg);
|
|
}
|
|
|
|
if (feedArgs.size() > result.config.feeds.size())
|
|
{
|
|
std::cerr << "Too many feed arguments" << std::endl;
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
size_t feedArgIndex = 0;
|
|
|
|
for (uint32_t slot = 0;
|
|
slot < result.config.feeds.size() && feedArgIndex < feedArgs.size();
|
|
++slot)
|
|
{
|
|
if (result.config.feeds[slot].kind == FeedKind::MxlSdk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const std::optional<FeedKind> parsed =
|
|
parseFeedKind(feedArgs[feedArgIndex]);
|
|
|
|
if (!parsed.has_value())
|
|
{
|
|
std::cerr << "Unknown feed kind: "
|
|
<< feedArgs[feedArgIndex] << std::endl;
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
result.config.feeds[slot].kind = parsed.value();
|
|
++feedArgIndex;
|
|
}
|
|
|
|
if (feedArgIndex < feedArgs.size())
|
|
{
|
|
std::cerr << "Too many feed arguments for available slots"
|
|
<< std::endl;
|
|
printUsage(argv[0]);
|
|
result.shouldExit = true;
|
|
result.exitCode = 1;
|
|
return result;
|
|
}
|
|
|
|
return result;
|
|
}
|