From cea9971b17f6e0ba8fe5abe83369c41224505539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20I=C3=B1igo=20Blasco?= Date: Tue, 5 May 2026 19:31:08 +0200 Subject: [PATCH] feat(pj_plugins): expose parser capability tags on the runtime catalog Propagate the parser capability tags already parsed from the plugin manifest JSON onto the host-visible RuntimeMessageParserPlugin so host code can discover them before instantiating the parser. Changes: - RuntimeMessageParserPlugin gains std::vector capabilities, populated by PluginRuntimeCatalog::loadAndRegisterMessageParser from PluginDescriptor::capabilities. - New example DSO mock_media_parser declares '"capabilities":["object"]' in its embedded manifest and exercises the dual-emit path (scalar seq field + opaque blob via objectWriteHost()->pushOwned()). Hosts that need to selectively register optional services (e.g. pj.parser_object_write.v1) before calling parser->bind() can now read the tag list directly from the catalog. The 'object' tag is the first real consumer of this channel; the example DSO doubles as a template for plugin authors that need to emit binary payloads alongside scalar fields. --- pj_plugins/CMakeLists.txt | 10 ++++ pj_plugins/examples/mock_media_parser.cpp | 50 +++++++++++++++++++ .../host/plugin_runtime_catalog.hpp | 1 + pj_plugins/src/plugin_runtime_catalog.cpp | 1 + 4 files changed, 62 insertions(+) create mode 100644 pj_plugins/examples/mock_media_parser.cpp diff --git a/pj_plugins/CMakeLists.txt b/pj_plugins/CMakeLists.txt index e8f2d68..478759a 100644 --- a/pj_plugins/CMakeLists.txt +++ b/pj_plugins/CMakeLists.txt @@ -124,6 +124,16 @@ target_compile_features(mock_schema_parser_plugin PRIVATE cxx_std_20) target_compile_options(mock_schema_parser_plugin PRIVATE ${PJ_WARNING_FLAGS}) target_link_libraries(mock_schema_parser_plugin PRIVATE pj_base) +# --------------------------------------------------------------------------- +# Mock Media Parser plugin (declares "object" capability — exercises the +# dual-emit path: scalar `seq` field + opaque blob via objectWriteHost()) +# --------------------------------------------------------------------------- + +add_library(mock_media_parser_plugin SHARED examples/mock_media_parser.cpp) +target_compile_features(mock_media_parser_plugin PRIVATE cxx_std_20) +target_compile_options(mock_media_parser_plugin PRIVATE ${PJ_WARNING_FLAGS}) +target_link_libraries(mock_media_parser_plugin PRIVATE pj_base) + endif() # PJ_BUILD_TESTS # --------------------------------------------------------------------------- diff --git a/pj_plugins/examples/mock_media_parser.cpp b/pj_plugins/examples/mock_media_parser.cpp new file mode 100644 index 0000000..8fe30ae --- /dev/null +++ b/pj_plugins/examples/mock_media_parser.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +#include "pj_base/sdk/message_parser_plugin_base.hpp" + +namespace { + +/// Example parser that exercises the dual-emit path: a scalar `seq` field +/// alongside a raw blob written to the ObjectStore. Payload envelope: +/// bytes [0..8) → little-endian uint64 seq +/// bytes [8..N) → opaque blob (e.g. compressed frame, image, serialized msg) +/// +/// The parser declares the `object` capability in its manifest, which signals +/// the host runtime to register the optional `pj.parser_object_write.v1` +/// service so `objectWriteHost()` is non-null inside parse(). +class MockMediaParser : public PJ::MessageParserPluginBase { + public: + PJ::Status parse(PJ::Timestamp timestamp_ns, PJ::Span payload) override { + if (!writeHostBound()) { + return PJ::unexpected(std::string("write host not bound")); + } + if (payload.size() < sizeof(uint64_t)) { + return PJ::unexpected(std::string("payload too small (need 8 bytes seq prefix)")); + } + + uint64_t seq = 0; + std::memcpy(&seq, payload.data(), sizeof(uint64_t)); + if (auto s = writeHost().appendRecord( + timestamp_ns, {{.name = "seq", .value = static_cast(seq)}}); + !s) { + return s; + } + + if (auto* obj = objectWriteHost()) { + std::vector body(payload.data() + sizeof(uint64_t), payload.data() + payload.size()); + if (auto s = obj->pushOwned(timestamp_ns, std::move(body)); !s) { + return s; + } + } + return PJ::okStatus(); + } +}; + +} // namespace + +PJ_MESSAGE_PARSER_PLUGIN( + MockMediaParser, + R"({"id":"mock-media-parser","name":"Mock Media Parser","version":"1.0.0","encoding":["mock-media"],"capabilities":["object"]})") diff --git a/pj_plugins/include/pj_plugins/host/plugin_runtime_catalog.hpp b/pj_plugins/include/pj_plugins/host/plugin_runtime_catalog.hpp index 4644dc3..b8109dd 100644 --- a/pj_plugins/include/pj_plugins/host/plugin_runtime_catalog.hpp +++ b/pj_plugins/include/pj_plugins/host/plugin_runtime_catalog.hpp @@ -33,6 +33,7 @@ struct RuntimeMessageParserPlugin { std::string id; std::string version; std::vector encodings; + std::vector capabilities; std::filesystem::file_time_type loaded_mtime; }; diff --git a/pj_plugins/src/plugin_runtime_catalog.cpp b/pj_plugins/src/plugin_runtime_catalog.cpp index 07d6a50..4e746d3 100644 --- a/pj_plugins/src/plugin_runtime_catalog.cpp +++ b/pj_plugins/src/plugin_runtime_catalog.cpp @@ -223,6 +223,7 @@ bool PluginRuntimeCatalog::loadAndRegisterMessageParser(const PluginDescriptor& loaded.name = descriptor.name; loaded.version = descriptor.version; loaded.encodings.insert(loaded.encodings.end(), descriptor.encoding.begin(), descriptor.encoding.end()); + loaded.capabilities = descriptor.capabilities; report(DiagnosticLevel::kInfo, loaded.id, "Loaded MessageParser " + loaded.name + " from " + loaded.path); message_parsers_.push_back(std::move(loaded));