18-05-26 result

This commit is contained in:
Johanness
2026-05-18 01:26:21 +03:00
parent ea7e73d6a4
commit ad215f32bc
34 changed files with 1616 additions and 395 deletions
+670 -24
View File
@@ -1,14 +1,313 @@
#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)
@@ -94,6 +393,278 @@ 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)
@@ -172,8 +743,11 @@ void printUsage(const char* executableName)
<< " --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 (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"
@@ -191,30 +765,6 @@ ConfigParseResult parseAppConfig(
{
ConfigParseResult result;
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;
}
applyDefaultFeeds(result.config);
for (int i = 1; i < argc; ++i)
@@ -252,6 +802,63 @@ ConfigParseResult parseAppConfig(
}
}
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)
@@ -276,12 +883,24 @@ ConfigParseResult parseAppConfig(
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)
@@ -310,6 +929,33 @@ ConfigParseResult parseAppConfig(
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)