diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fbdcf1..13fce2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,12 +36,11 @@ add_library(cvmmap_streamer_common STATIC src/core/frame_source.cpp src/core/ingest_runtime.cpp src/ipc/contracts.cpp - src/ipc/ipc_stub.cpp src/sim/wire.cpp lib/cvmmap-client-cpp/app_cvmmap_client.cpp lib/cvmmap-client-cpp/app_cvmmap_parser.cpp src/metrics/latency_tracker.cpp - src/pipeline/pipeline_stub.cpp + src/pipeline/pipeline_runtime.cpp src/protocol/rtmp_publisher.cpp src/protocol/rtp_publisher.cpp) diff --git a/include/cvmmap_streamer/common.h b/include/cvmmap_streamer/common.h index d13e602..6b743f8 100644 --- a/include/cvmmap_streamer/common.h +++ b/include/cvmmap_streamer/common.h @@ -1,13 +1,11 @@ #pragma once -#include #include namespace cvmmap_streamer { void print_help(std::string_view executable); bool has_help_flag(int argc, char **argv); -std::size_t sample_ipc_payload_size(); void pipeline_tick(); void protocol_step(); diff --git a/include/cvmmap_streamer/sim/options.hpp b/include/cvmmap_streamer/sim/options.hpp deleted file mode 100644 index 56f99cd..0000000 --- a/include/cvmmap_streamer/sim/options.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "cvmmap_streamer/ipc/contracts.hpp" - -namespace cvmmap_streamer::sim { - -struct RuntimeConfig { - std::uint32_t frames{360}; - std::uint32_t fps{60}; - std::uint16_t width{64}; - std::uint16_t height{48}; - std::optional emit_reset_at{}; - std::optional emit_reset_every{}; - std::optional switch_format_at{}; - std::optional switch_width{}; - std::optional switch_height{}; - std::string label{"sim"}; - std::string shm_name{"cvmmap_sim"}; - std::string zmq_endpoint{"ipc:///tmp/cvmmap_sim"}; - std::uint8_t channels{3}; - ipc::Depth depth{ipc::Depth::U8}; - ipc::PixelFormat pixel_format{ipc::PixelFormat::BGR}; - - [[nodiscard]] - std::uint32_t payload_size_bytes() const; -}; - -enum class ParseStatus { - Ok, - Help, - Error, -}; - -struct ParseResult { - ParseStatus status{ParseStatus::Ok}; - RuntimeConfig config{}; - std::string message{}; - int exit_code{0}; -}; - -std::expected parse_runtime_config(int argc, - char **argv); -void print_help(); -ParseResult parse_runtime_config_with_cli11( - int argc, char **argv, std::string_view executable_name = "cvmmap_sim"); - -} // namespace cvmmap_streamer::sim diff --git a/src/ipc/ipc_stub.cpp b/src/ipc/ipc_stub.cpp deleted file mode 100644 index 4d9d6ed..0000000 --- a/src/ipc/ipc_stub.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include - -#include "cvmmap_streamer/ipc/contracts.hpp" - -namespace cvmmap_streamer { - -std::size_t sample_ipc_payload_size() { - return ipc::kShmPayloadOffset; -} - -} diff --git a/src/main_sim.cpp b/src/main_sim.cpp deleted file mode 100644 index 648c25d..0000000 --- a/src/main_sim.cpp +++ /dev/null @@ -1,280 +0,0 @@ -#include "cvmmap_streamer/common.h" -#include "cvmmap_streamer/ipc/contracts.hpp" -#include "cvmmap_streamer/sim/options.hpp" -#include "cvmmap_streamer/sim/wire.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace { - -namespace ipc = cvmmap_streamer::ipc; - -class SharedMemoryRegion { -public: - static std::expected create(const std::string &name, std::size_t bytes) { - const std::string shm_name = "/" + name; - const int fd = shm_open(shm_name.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - if (fd < 0) { - return std::unexpected("shm_open failed"); - } - - if (ftruncate(fd, static_cast(bytes)) != 0) { - close(fd); - shm_unlink(shm_name.c_str()); - return std::unexpected("ftruncate failed"); - } - - auto *mapped = static_cast(mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); - if (mapped == MAP_FAILED) { - close(fd); - shm_unlink(shm_name.c_str()); - return std::unexpected("mmap failed"); - } - - std::memset(mapped, 0, bytes); - return SharedMemoryRegion(shm_name, fd, mapped, bytes); - } - - SharedMemoryRegion(const SharedMemoryRegion &) = delete; - SharedMemoryRegion &operator=(const SharedMemoryRegion &) = delete; - - SharedMemoryRegion(SharedMemoryRegion &&other) noexcept - : name_(std::move(other.name_)), - fd_(other.fd_), - ptr_(other.ptr_), - bytes_(other.bytes_) { - other.fd_ = -1; - other.ptr_ = nullptr; - other.bytes_ = 0; - } - - SharedMemoryRegion &operator=(SharedMemoryRegion &&other) noexcept { - if (this == &other) { - return *this; - } - cleanup(); - name_ = std::move(other.name_); - fd_ = other.fd_; - ptr_ = other.ptr_; - bytes_ = other.bytes_; - other.fd_ = -1; - other.ptr_ = nullptr; - other.bytes_ = 0; - return *this; - } - - ~SharedMemoryRegion() { - cleanup(); - } - - [[nodiscard]] - std::span metadata() { - return std::span(ptr_, ipc::kShmPayloadOffset); - } - - [[nodiscard]] - std::span payload(std::size_t payload_bytes) { - return std::span(ptr_ + ipc::kShmPayloadOffset, payload_bytes); - } - -private: - SharedMemoryRegion(std::string name, int fd, std::uint8_t *ptr, std::size_t bytes) - : name_(std::move(name)), fd_(fd), ptr_(ptr), bytes_(bytes) {} - - void cleanup() { - if (ptr_ != nullptr && bytes_ > 0) { - munmap(ptr_, bytes_); - ptr_ = nullptr; - } - if (fd_ >= 0) { - close(fd_); - fd_ = -1; - } - if (!name_.empty()) { - shm_unlink(name_.c_str()); - } - } - - std::string name_; - int fd_{-1}; - std::uint8_t *ptr_{nullptr}; - std::size_t bytes_{0}; -}; - -void cleanup_zmq_ipc_path(std::string_view endpoint) { - constexpr std::string_view kPrefix{"ipc://"}; - if (!endpoint.starts_with(kPrefix)) { - return; - } - const auto path = endpoint.substr(kPrefix.size()); - if (!path.empty()) { - unlink(std::string(path).c_str()); - } -} - -} - -int main(int argc, char **argv) { - if (cvmmap_streamer::has_help_flag(argc, argv)) { - cvmmap_streamer::sim::print_help(); - return 0; - } - - auto config = cvmmap_streamer::sim::parse_runtime_config(argc, argv); - if (!config) { - spdlog::error("{}", config.error()); - cvmmap_streamer::sim::print_help(); - return 2; - } - - const auto payload_bytes = static_cast(config->payload_size_bytes()); - const auto switched_width = config->switch_width.value_or(config->width); - const auto switched_height = config->switch_height.value_or(config->height); - const auto switched_payload_bytes = - static_cast(switched_width) * - static_cast(switched_height) * - static_cast(config->channels); - const auto payload_bytes_max = std::max(payload_bytes, switched_payload_bytes); - const auto shm_bytes = ipc::kShmPayloadOffset + payload_bytes_max; - - auto shm = SharedMemoryRegion::create(config->shm_name, shm_bytes); - if (!shm) { - spdlog::error("failed to create shared memory '{}': {}", config->shm_name, shm.error()); - return 3; - } - - cleanup_zmq_ipc_path(config->zmq_endpoint); - std::optional publisher; - try { - static zmq::context_t context{1}; - publisher.emplace(context, zmq::socket_type::pub); - publisher->bind(config->zmq_endpoint); - } catch (const zmq::error_t &e) { - spdlog::error("failed to bind zmq endpoint '{}': {}", config->zmq_endpoint, e.what()); - return 4; - } - - ipc::FrameInfo frame_info{ - .width = config->width, - .height = config->height, - .channels = config->channels, - .depth = config->depth, - .pixel_format = config->pixel_format, - .buffer_size = config->payload_size_bytes()}; - - std::array sync_msg{}; - std::array status_msg{}; - - const auto send_status = [&](ipc::ModuleStatus status, std::uint32_t frame_count) { - cvmmap_streamer::sim::write_module_status_message(status_msg, config->label, status); - publisher->send(zmq::buffer(status_msg), zmq::send_flags::none); - spdlog::info("status={} frame_count={}", static_cast(status), frame_count); - }; - - std::uint64_t timestamp_ns = 1'000'000'000ull; - const std::uint64_t tick_ns = - (config->fps == 0) - ? 0ull - : std::max(1ull, 1'000'000'000ull / static_cast(config->fps)); - - spdlog::info( - "sim start shm='{}' zmq='{}' label='{}' frames={} fps={} {}x{} payload={}", - config->shm_name, - config->zmq_endpoint, - config->label, - config->frames, - config->fps, - config->width, - config->height, - payload_bytes); - - send_status(ipc::ModuleStatus::Online, 0); - - bool reset_sent{false}; - std::uint32_t reset_every_count{0}; - bool format_switched{false}; - for (std::uint32_t frame_count = 1; frame_count <= config->frames; ++frame_count) { - if (!format_switched && config->switch_format_at && *config->switch_format_at == frame_count) { - frame_info.width = switched_width; - frame_info.height = switched_height; - frame_info.buffer_size = static_cast(switched_payload_bytes); - format_switched = true; - spdlog::info( - "sim format switch at frame={} new={}x{} channels={} payload={}", - frame_count, - frame_info.width, - frame_info.height, - static_cast(frame_info.channels), - frame_info.buffer_size); - } - - const auto active_payload_bytes = static_cast(frame_info.buffer_size); - auto payload = shm->payload(active_payload_bytes); - cvmmap_streamer::sim::write_deterministic_payload( - payload, - frame_count, - frame_info.width, - frame_info.height, - config->channels); - - cvmmap_streamer::sim::write_frame_metadata( - shm->metadata(), - frame_info, - frame_count, - timestamp_ns); - - cvmmap_streamer::sim::write_sync_message( - sync_msg, - config->label, - frame_count, - timestamp_ns); - publisher->send(zmq::buffer(sync_msg), zmq::send_flags::none); - - spdlog::info("sync frame_count={} timestamp_ns={}", frame_count, timestamp_ns); - - if (!reset_sent && config->emit_reset_at && *config->emit_reset_at == frame_count) { - send_status(ipc::ModuleStatus::StreamReset, frame_count); - reset_sent = true; - } - - if (config->emit_reset_every && *config->emit_reset_every > 0 && (frame_count % *config->emit_reset_every) == 0) { - send_status(ipc::ModuleStatus::StreamReset, frame_count); - reset_sent = true; - reset_every_count += 1; - } - - timestamp_ns += tick_ns; - if (config->fps > 0) { - std::this_thread::sleep_for(std::chrono::nanoseconds(tick_ns)); - } - } - - send_status(ipc::ModuleStatus::Offline, config->frames); - cleanup_zmq_ipc_path(config->zmq_endpoint); - spdlog::info( - "sim complete frames={} reset_sent={} periodic_resets={} format_switched={}", - config->frames, - reset_sent ? "true" : "false", - reset_every_count, - format_switched ? "true" : "false"); - return 0; -} diff --git a/src/pipeline/pipeline_stub.cpp b/src/pipeline/pipeline_runtime.cpp similarity index 100% rename from src/pipeline/pipeline_stub.cpp rename to src/pipeline/pipeline_runtime.cpp diff --git a/src/protocol/protocol_stub.cpp b/src/protocol/protocol_stub.cpp deleted file mode 100644 index cf5d23f..0000000 --- a/src/protocol/protocol_stub.cpp +++ /dev/null @@ -1,5 +0,0 @@ -namespace cvmmap_streamer { - -void protocol_step() {} - -} diff --git a/src/sim/options.cpp b/src/sim/options.cpp deleted file mode 100644 index 15bc9a4..0000000 --- a/src/sim/options.cpp +++ /dev/null @@ -1,411 +0,0 @@ -#include "cvmmap_streamer/sim/options.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace cvmmap_streamer::sim { - -namespace { - -std::string trim_copy(std::string value) { - auto not_space = [](unsigned char c) { - return c != ' ' && c != '\t' && c != '\n' && c != '\r'; - }; - while (!value.empty() && - !not_space(static_cast(value.front()))) { - value.erase(value.begin()); - } - while (!value.empty() && - !not_space(static_cast(value.back()))) { - value.pop_back(); - } - return value; -} - -std::string normalize_cli_error(std::string raw_message) { - if (raw_message.find("The following argument was not expected:") != - std::string::npos) { - const auto pos = raw_message.find(':'); - if (pos != std::string::npos && pos + 1 < raw_message.size()) { - return "unknown argument: '" + trim_copy(raw_message.substr(pos + 1)) + - "'"; - } - return "unknown argument"; - } - - constexpr std::array kFlags{"--frames", - "--fps", - "--width", - "--height", - "--emit-reset-at", - "--emit-reset-every", - "--switch-format-at", - "--switch-width", - "--switch-height", - "--label", - "--shm-name", - "--zmq-endpoint"}; - - if (raw_message.find("requires at least") != std::string::npos || - raw_message.find("requires ") != std::string::npos) { - for (const auto flag : kFlags) { - if (raw_message.find(flag) != std::string::npos) { - return "missing value for " + std::string(flag); - } - } - } - - return trim_copy(std::move(raw_message)); -} - -std::expected -parse_u32(std::string_view raw, std::string_view flag_name) { - unsigned long parsed{0}; - const auto *begin = raw.data(); - const auto *end = raw.data() + raw.size(); - const auto result = std::from_chars(begin, end, parsed, 10); - if (result.ec != std::errc{} || result.ptr != end) { - return std::unexpected("invalid value for " + std::string(flag_name) + - ": '" + std::string(raw) + "'"); - } - if (parsed > std::numeric_limits::max()) { - return std::unexpected("value out of range for " + std::string(flag_name) + - ": '" + std::string(raw) + "'"); - } - return static_cast(parsed); -} - -std::expected -parse_u16(std::string_view raw, std::string_view flag_name) { - unsigned int parsed{0}; - const auto *begin = raw.data(); - const auto *end = raw.data() + raw.size(); - const auto result = std::from_chars(begin, end, parsed, 10); - if (result.ec != std::errc{} || result.ptr != end) { - return std::unexpected("invalid value for " + std::string(flag_name) + - ": '" + std::string(raw) + "'"); - } - if (parsed > std::numeric_limits::max()) { - return std::unexpected("value out of range for " + std::string(flag_name) + - ": '" + std::string(raw) + "'"); - } - return static_cast(parsed); -} - -std::expected -validate_runtime_config(const RuntimeConfig &config) { - if (config.frames == 0) { - return std::unexpected("--frames must be > 0"); - } - if (config.width == 0 || config.height == 0) { - return std::unexpected("--width and --height must be > 0"); - } - if (config.label.empty()) { - return std::unexpected("--label must not be empty"); - } - if (config.label.size() > ipc::kLabelLenMax) { - return std::unexpected("--label exceeds 24 bytes"); - } - if (config.shm_name.empty()) { - return std::unexpected("--shm-name must not be empty"); - } - if (config.zmq_endpoint.empty()) { - return std::unexpected("--zmq-endpoint must not be empty"); - } - if (config.emit_reset_at && *config.emit_reset_at == 0) { - return std::unexpected("--emit-reset-at must be in [1, --frames]"); - } - if (config.emit_reset_at && *config.emit_reset_at > config.frames) { - return std::unexpected("--emit-reset-at must be in [1, --frames]"); - } - if (config.emit_reset_every && *config.emit_reset_every == 0) { - return std::unexpected("--emit-reset-every must be > 0"); - } - if (config.switch_format_at && *config.switch_format_at == 0) { - return std::unexpected("--switch-format-at must be in [1, --frames]"); - } - if (config.switch_format_at && *config.switch_format_at > config.frames) { - return std::unexpected("--switch-format-at must be in [1, --frames]"); - } - if (config.switch_width && *config.switch_width == 0) { - return std::unexpected("--switch-width must be > 0"); - } - if (config.switch_height && *config.switch_height == 0) { - return std::unexpected("--switch-height must be > 0"); - } - - const auto payload_size = static_cast(config.width) * - static_cast(config.height) * - static_cast(config.channels); - if (payload_size > - static_cast(std::numeric_limits::max())) { - return std::unexpected( - "computed payload size exceeds cv-mmap frame_info.buffer_size range"); - } - - const auto switched_width = config.switch_width.value_or(config.width); - const auto switched_height = config.switch_height.value_or(config.height); - const auto switched_payload_size = - static_cast(switched_width) * - static_cast(switched_height) * - static_cast(config.channels); - if (switched_payload_size > - static_cast(std::numeric_limits::max())) { - return std::unexpected("computed switched payload size exceeds cv-mmap " - "frame_info.buffer_size range"); - } - - return {}; -} - -std::string build_help_text(std::string_view executable_name = "cvmmap_sim") { - RuntimeConfig defaults{}; - std::string frames_raw; - std::string fps_raw; - std::string width_raw; - std::string height_raw; - std::string emit_reset_at_raw; - std::string emit_reset_every_raw; - std::string switch_format_at_raw; - std::string switch_width_raw; - std::string switch_height_raw; - std::string label_raw; - std::string shm_name_raw; - std::string zmq_endpoint_raw; - - CLI::App app{"cv-mmap deterministic frame simulator"}; - app.name(std::string(executable_name)); - app.allow_extras(false); - app.set_help_flag("--help,-h", "show this message"); - - app.add_option("--frames", frames_raw, "total frames to emit (default: 360)"); - app.add_option("--fps", fps_raw, - "deterministic frame pacing, 0 disables sleep (default: 60)"); - app.add_option("--width", width_raw, "frame width (default: 64)"); - app.add_option("--height", height_raw, "frame height (default: 48)"); - app.add_option("--emit-reset-at", emit_reset_at_raw, - "emit MODULE_STATUS_STREAM_RESET at frame_count n"); - app.add_option("--emit-reset-every", emit_reset_every_raw, - "emit MODULE_STATUS_STREAM_RESET each n frames"); - app.add_option("--switch-format-at", switch_format_at_raw, - "switch metadata frame format at frame_count n"); - app.add_option("--switch-width", switch_width_raw, - "width after format switch (default: --width)"); - app.add_option("--switch-height", switch_height_raw, - "height after format switch (default: --height)"); - app.add_option("--label", label_raw, "stream label (<=24 bytes)") - ->default_val(defaults.label); - app.add_option("--shm-name", shm_name_raw, "POSIX shm object name") - ->default_val(defaults.shm_name); - app.add_option("--zmq-endpoint", zmq_endpoint_raw, "ZMQ pub endpoint") - ->default_val(defaults.zmq_endpoint); - - return app.help(); -} - -} // namespace - -std::uint32_t RuntimeConfig::payload_size_bytes() const { - const auto width64 = static_cast(width); - const auto height64 = static_cast(height); - const auto channels64 = static_cast(channels); - return static_cast(width64 * height64 * channels64); -} - -void print_help() { - std::istringstream in(build_help_text()); - for (std::string line; std::getline(in, line);) { - spdlog::info("{}", line); - } -} - -ParseResult parse_runtime_config_with_cli11(int argc, char **argv, - std::string_view executable_name) { - RuntimeConfig config{}; - - std::string frames_raw; - std::string fps_raw; - std::string width_raw; - std::string height_raw; - std::string emit_reset_at_raw; - std::string emit_reset_every_raw; - std::string switch_format_at_raw; - std::string switch_width_raw; - std::string switch_height_raw; - std::string label_raw; - std::string shm_name_raw; - std::string zmq_endpoint_raw; - - CLI::App app{"cv-mmap deterministic frame simulator"}; - app.name(std::string(executable_name)); - app.allow_extras(false); - app.set_help_flag("--help,-h", "show this message"); - - app.add_option("--frames", frames_raw, "total frames to emit (default: 360)"); - app.add_option("--fps", fps_raw, - "deterministic frame pacing, 0 disables sleep (default: 60)"); - app.add_option("--width", width_raw, "frame width (default: 64)"); - app.add_option("--height", height_raw, "frame height (default: 48)"); - app.add_option("--emit-reset-at", emit_reset_at_raw, - "emit MODULE_STATUS_STREAM_RESET at frame_count n"); - app.add_option("--emit-reset-every", emit_reset_every_raw, - "emit MODULE_STATUS_STREAM_RESET each n frames"); - app.add_option("--switch-format-at", switch_format_at_raw, - "switch metadata frame format at frame_count n"); - app.add_option("--switch-width", switch_width_raw, - "width after format switch (default: --width)"); - app.add_option("--switch-height", switch_height_raw, - "height after format switch (default: --height)"); - app.add_option("--label", label_raw, "stream label (<=24 bytes)"); - app.add_option("--shm-name", shm_name_raw, "POSIX shm object name"); - app.add_option("--zmq-endpoint", zmq_endpoint_raw, "ZMQ pub endpoint"); - - if (argc <= 1) { - return ParseResult{ - .status = ParseStatus::Help, .message = app.help(), .exit_code = 0}; - } - - try { - app.parse(argc, argv); - } catch (const CLI::CallForHelp &) { - return ParseResult{ - .status = ParseStatus::Help, .message = app.help(), .exit_code = 0}; - } catch (const CLI::ParseError &e) { - return ParseResult{.status = ParseStatus::Error, - .message = normalize_cli_error(e.what()), - .exit_code = 2}; - } - - if (!frames_raw.empty()) { - auto parsed = parse_u32(frames_raw, "--frames"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.frames = *parsed; - } - - if (!fps_raw.empty()) { - auto parsed = parse_u32(fps_raw, "--fps"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.fps = *parsed; - } - - if (!width_raw.empty()) { - auto parsed = parse_u16(width_raw, "--width"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.width = *parsed; - } - - if (!height_raw.empty()) { - auto parsed = parse_u16(height_raw, "--height"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.height = *parsed; - } - - if (!emit_reset_at_raw.empty()) { - auto parsed = parse_u32(emit_reset_at_raw, "--emit-reset-at"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.emit_reset_at = *parsed; - } - - if (!emit_reset_every_raw.empty()) { - auto parsed = parse_u32(emit_reset_every_raw, "--emit-reset-every"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.emit_reset_every = *parsed; - } - - if (!switch_format_at_raw.empty()) { - auto parsed = parse_u32(switch_format_at_raw, "--switch-format-at"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.switch_format_at = *parsed; - } - - if (!switch_width_raw.empty()) { - auto parsed = parse_u16(switch_width_raw, "--switch-width"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.switch_width = *parsed; - } - - if (!switch_height_raw.empty()) { - auto parsed = parse_u16(switch_height_raw, "--switch-height"); - if (!parsed) { - return ParseResult{.status = ParseStatus::Error, - .message = parsed.error(), - .exit_code = 2}; - } - config.switch_height = *parsed; - } - - if (!label_raw.empty()) { - config.label = label_raw; - } - if (!shm_name_raw.empty()) { - config.shm_name = shm_name_raw; - } - if (!zmq_endpoint_raw.empty()) { - config.zmq_endpoint = zmq_endpoint_raw; - } - - auto validation = validate_runtime_config(config); - if (!validation) { - return ParseResult{.status = ParseStatus::Error, - .message = validation.error(), - .exit_code = 2}; - } - - return ParseResult{ - .status = ParseStatus::Ok, .config = config, .exit_code = 0}; -} - -std::expected parse_runtime_config(int argc, - char **argv) { - auto parsed = parse_runtime_config_with_cli11(argc, argv); - if (parsed.status == ParseStatus::Ok) { - return parsed.config; - } - if (!parsed.message.empty()) { - return std::unexpected(parsed.message); - } - return std::unexpected("failed to parse runtime options"); -} - -} // namespace cvmmap_streamer::sim