Skip to main content

plotjuggler-plugin

Write or modify a PlotJuggler plugin (DataSource, MessageParser, Toolbox, or Dialog) or native functional parser module using the plotjuggler_sdk. Use this whenever the task is to implement, extend, debug, or build a PlotJuggler extension — importing a file format, streaming live data, parsing message payloads, mapping a custom type to a canonical object or scalars, adding a data-processing toolbox, or building a plugin configuration dialog — even if the user does not say the word "plugin". It gives the fast path, correct CMake, and load-bearing ABI/lifetime rules. Do NOT use it for changing the SDK's own ABI/protocol (that is a maintainer task with different rules — see below).

ソース情報

リポジトリ
PlotJuggler/plotjuggler_sdk
ソースの最終更新活動
2026年9月6日 22:21
検出された SKILL.md の言語
英語
スター
11
フォーク
2

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
7 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
plotjuggler-plugin
description
Write or modify a PlotJuggler plugin (DataSource, MessageParser, Toolbox, or Dialog) or native functional parser module using the plotjuggler_sdk. Use this whenever the task is to implement, extend, debug, or build a PlotJuggler extension — importing a file format, streaming live data, parsing message payloads, mapping a custom type to a canonical object or scalars, adding a data-processing toolbox, or building a plugin configuration dialog — even if the user does not say the word "plugin". It gives the fast path, correct CMake, and load-bearing ABI/lifetime rules. Do NOT use it for changing the SDK's own ABI/protocol (that is a maintainer task with different rules — see below).
# Writing a PlotJuggler plugin PlotJuggler has four **plugin families**. Subclass a family's small C++20 base class and export it from a shared library (`.so`/`.dll`/`.dylib`). `plotjuggler_sdk` supplies each base class. A one-line macro emits the entry point, version symbol, exception trampolines and symbol folding. **You never touch the raw ABI.** Override a few virtual methods and ship the library. There is also a lighter extension shape: a **functional parser module** is one C++17 source compiled as a native shared library, with no plugin-family boilerplate. Write a MessageParser plugin when you own a whole encoding. Write a parser module when you only need one or a few custom message types rendered as canonical objects or scalars. Start at `references/parser-module.md`. This skill is the author-oriented fast path. The repo's `pj_plugins/docs/` guides are the full reference; this steers you to the right one and front-loads the things that silently break a plugin. ## Are you writing a plugin, or changing the SDK? - **Writing a plugin *against* the SDK** → this skill. The macros handle ABI versioning, tail slots, `struct_size`, weak linkage, exception safety. You do **not** need to read the ABI-stability rules. - **Changing the SDK's own ABI/protocol** (adding vtable slots, editing a struct, bumping a `PJ_*_PROTOCOL_VERSION`, touching `pj_base/abi/baseline.abi`) → this is a **maintainer** task. Stop and follow `pj_plugins/docs/ARCHITECTURE.md` §0a and the "Release Versioning" contract in the root `CLAUDE.md`. Different rules apply. ## Step 1 — How do you get the SDK? A plugin author obtains `plotjuggler_sdk` one of three ways. **All three end at the same link line** — `target_link_libraries(my_plugin PRIVATE plotjuggler_sdk::plugin_sdk)` — only the acquisition step differs: - **Conan package** (`plotjuggler_sdk`) — the common external route; the official plugins use it. Pin the compatibility range explicitly: `plotjuggler_sdk/[>=X.Y.Z <(X+1).0.0]`, where `X.Y.Z` is the version that introduced the newest SDK feature you actually use (pre-1.0 the upper bound is `<1.0.0` — the SDK guarantees no breaks within `0.x`). Tip from the official repo: keep the version in a single `SDK_VERSION` file your `conanfile.py` reads. Then `find_package(plotjuggler_sdk REQUIRED COMPONENTS plugin_sdk)`. - **pixi / conda package** (`plotjuggler_sdk`) — same `find_package` + target once the package is in your environment. - **git submodule / vendored source** — `add_subdirectory(plotjuggler_sdk)`; the same `plotjuggler_sdk::plugin_sdk` alias exists in-tree, and `pj_configure_plugin` / `pj_embed_file` are available too, so the CMake below is identical. (Editing the SDK repo itself is this world: the raw umbrella target is `pj_plugin_sdk`; bare `pj_base` lacks the MessageParser and dialog SDK headers, so prefer the umbrella. The `pj_plugins/docs/*-guide.md` snippets show per-piece in-tree targets.) ## Step 2 — Pick the family | Your goal | Family | Read | |---|---|---| | Turn a **file** or a **live source** (socket, serial, hardware) into topics | **DataSource** | `references/data-source.md` | | Decode a **byte payload** on a topic into named fields (JSON, protobuf, ROS, custom) | **MessageParser** | `references/message-parser.md` | | Map **one or a few message types** to canonical objects or scalars without owning the encoding | **Functional parser module** | `references/parser-module.md` | | A **tool** that reads existing data, transforms it, and writes new topics | **Toolbox** | `references/toolbox.md` | | A **configuration UI** (for a source/parser/toolbox, or standalone) | **Dialog** | `references/dialog.md` | A single `.so` can host **more than one family** — the supported pattern is an owner + its embedded Dialog (one `PJ_*_PLUGIN(...)` macro per family at file scope). Don't ship *unrelated* families in one DSO: catalog discovery reads one primary family descriptor per library. If your data is object-like (image, point cloud, occupancy grid, transforms, markers…) rather than scalar time series, also read `references/builtin-objects.md` **before** writing — it changes what you emit and how. ## Step 3 — The fast path per family Subclass the base, override the minimum, register with the macro. The macro's second argument is the **manifest JSON literal** (see Step 5). | Family | Subclass | `#include` | Override (minimum) | Register | |---|---|---|---|---| | DataSource — file import | `PJ::FileSourceBase` | `pj_base/sdk/data_source_patterns.hpp` | `extraCapabilities()`, `importData()` | `PJ_DATA_SOURCE_PLUGIN(C, m)` | | DataSource — live stream | `PJ::StreamSourceBase` | `pj_base/sdk/data_source_patterns.hpp` | `extraCapabilities()`, `onStart/onPoll/onStop()` | `PJ_DATA_SOURCE_PLUGIN(C, m)` | | DataSource — manual | `PJ::DataSourcePluginBase` | `pj_base/sdk/data_source_plugin_base.hpp` | `capabilities()`, `start/stop()`, `currentState()` | `PJ_DATA_SOURCE_PLUGIN(C, m)` | | MessageParser | `PJ::MessageParserPluginBase` | ⚠ `pj_plugins/sdk/message_parser_plugin_base.hpp` | `registerSchemaHandler()` in ctor/`bindSchema()` | `PJ_MESSAGE_PARSER_PLUGIN(C, m)` | | Toolbox | `PJ::ToolboxPluginBase` | `pj_base/sdk/toolbox_plugin_base.hpp` | `capabilities()` (+ data ops) | `PJ_TOOLBOX_PLUGIN(C, m)` | | Dialog | `PJ::DialogPluginTyped` | `pj_plugins/sdk/dialog_plugin_typed.hpp` + `.../widget_data.hpp` | `manifest()`, `ui_content()`, `widget_data()`, event handlers | `PJ_DIALOG_PLUGIN(C, m)` | > ⚠ **Header-location trap.** Three base classes live under `pj_base/sdk/`, but > `MessageParserPluginBase` lives under **`pj_plugins/sdk/`**, and the Dialog SDK > lives under `pj_plugins/sdk/` too (installed there from the `dialog_protocol` > module). Do not substitute a `pj_base/sdk/` path for either one; use the paths > in the table. The quickest correct start is to copy a working example from this repo and edit it: `examples/sdk_consumer/` (minimal external DataSource with the full CMake), `pj_plugins/examples/mock_*.cpp` (DataSource, parser, toolbox, source+dialog), and `pj_plugins/dialog_protocol/examples/mock_dialog.cpp` (standalone dialog). Each family reference embeds an API skeleton for when the repo isn't at hand (domain placeholders like `openSocket()` are yours to fill in). ## Step 4 — The SDK already has it: do not write these Before writing a helper, consult [Existing SDK utilities](../../../docs/sdk-utilities.md) and read its header. The table covers source-provider, numeric/time, streaming, dialog and testing support, including helpers owned by pj-official-plugins. Providers also follow [the provider contract](../../../docs/provider-guide.md) and link `plotjuggler_sdk::source` alongside `plugin_sdk`. ## Step 5 — Build it One CMakeLists.txt serves every acquisition channel from Step 1: ```cmake cmake_minimum_required(VERSION 3.22) project(my_plugin LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Conan / pixi / installed package: find_package(plotjuggler_sdk REQUIRED COMPONENTS plugin_sdk) # Submodule / vendored source instead: add_subdirectory(plotjuggler_sdk) add_library(my_plugin SHARED my_plugin.cpp) target_link_libraries(my_plugin PRIVATE plotjuggler_sdk::plugin_sdk) # same in all worlds pj_configure_plugin(my_plugin FAMILIES data_source # data_source | message_parser | toolbox | dialog # (list several: `data_source dialog` for an embedded dialog) MANIFEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/manifest.json MANIFEST_HEADER generated/my_manifest.hpp # -> #include "my_manifest.hpp" MANIFEST_VAR kMyManifest # -> PJ_DATA_SOURCE_PLUGIN(MyPlugin, kMyManifest) ) # Dialog UI kept as a real Qt Designer file: # pj_embed_file(my_plugin FILE ui/my_dialog.ui HEADER generated/my_dialog_ui.hpp VAR_NAME kMyDialogUi) ``` The `plotjuggler_sdk::plugin_sdk` component supplies the common author surface — base + parser SDK + dialog SDK + these CMake helpers. Compiled provider support requires `plotjuggler_sdk::source` as well. You do **not** link a separate dialog target downstream (`pj_dialog_sdk` is an in-tree name). **`pj_configure_plugin` is required in practice.** It: 1. Validates `manifest.json`; `id`, `name` and `version` are required. 2. Isolates plugin symbols from the host with hidden visibility everywhere and `-Wl,-Bsymbolic-functions` on Linux/ELF. 3. Writes a human-readable `<target>.pjmanifest.json` sidecar for tooling. Runtime discovery reads the JSON embedded by `PJ_*_PLUGIN`, not this sidecar. 4. With `MANIFEST_HEADER`, generates the `constexpr` header passed to `PJ_*_PLUGIN(Class, kMyManifest)`. `manifest.json` remains the single source. 5. On Linux, links a version-script allowlist exporting **only** ABI entry points. The post-link check fails the build if an entry point is missing or a `STB_GNU_UNIQUE` symbol leaks. MessageParser manifests **must** include `"encoding": ["json", …]`. The host routes payloads by these case-sensitive names. DataSource may add `"file_extensions": [".csv"]`. `pj_emit_plugin_manifest` (the pre-0.25 name) still works but is deprecated. ### `min_sdk_required` — the host floor, not the build pin `min_sdk_required` declares the oldest **host** SDK contract your plugin functions on. It is independent of the SDK you build against: `pj_base` / `pj_source` are static libraries compiled into your plugin, so adopting a new helper (`parseIso8601Utc`, `sliderToWindow`, a codec) **never** raises the floor. Only **host-contract** surfaces do — runtime-host tail slots, named services, wire contracts — and the authority for which release introduced each is `feature_floors.json`, shipped in the package at `share/plotjuggler_sdk/feature_floors.json`. When you adopt a surface newer than your floor, pick one: - **Raise `min_sdk_required`** to that surface's release — mandatory for a non-negotiated (hard protocol) surface, and right whenever the feature is essential to what your plugin is for. The reverse is NOT symmetric: the checker sees only runtime host surfaces, so a floor above anything it matched may still be correct (compile-time SDK APIs are outside the check) — never lower a floor just because the checker matched nothing. - **Declare the degraded range** — when the surface is runtime-negotiated and the wrapper's error return is acceptable (ignore it and carry on). Two manifest fields, required together and forbidden when nothing exceeds the floor: ```json "min_sdk_required": "0.28.0", "suggested_sdk_version": "0.32.0", "floor_test": "McapDatasetMetadata.MetadataPublishDegradesCleanlyOnFloorLevelHost" ``` `suggested_sdk_version` is the full-feature floor (exactly the max introducing release over every host surface you use — the checker validates it), so hosts can tell users which PlotJuggler unlocks your whole feature set. `floor_test` names one gtest, existing in your `tests/`, proving the DEGRADED PATH works against a floor-level host — drive it with a `struct_size`-truncated host vtable and poison the slots past the truncation so a missing guard fails the test. Helper-level granularity is acceptable when the full entry point needs a live host; the test comment must then state the routing fact (which entry point routes through the helper). Neither field is ever a gate: `min_sdk_required` remains the only admission criterion, and `min_plotjuggler_version` is deprecated. Check your own plugin with the tool the SDK package ships next to the table: ```bash python3 <sdk>/share/plotjuggler_sdk/feature_floor_check.py \ --table <sdk>/share/plotjuggler_sdk/feature_floors.json \ --manifest my_plugin/manifest.json --sources my_plugin ``` ## Step 6 — The rules that silently break a plugin These cut across all families. The family references add family-specific traps (dialog `buttonBox` naming, parser topic-scoping, toolbox `notifyDataChanged` coalescing, etc.) — read them. 1. **Never let an exception cross the ABI boundary.** Return `PJ::unexpected("clear reason")` / `PJ::okStatus()` from fallible methods. The macro's trampolines are only a safety net: fallible entry points convert a stray exception's `what()` into the ABI error, non-fallible ones can only swallow it, and an exception escaping a `noexcept` slot terminates the process. Explicit `Status` returns are the only predictable diagnostics. 2. **Keep the data plane on the host's callback thread.** `writeHost()`, `toolboxHost()`, and `pushMessage()` are single-threaded — a background I/O thread must buffer under a mutex and hand data off inside `onPoll()`/`poll()`. The authoritative source is the thread tag on each slot in the protocol headers: slots tagged `[thread-safe]` — `reportMessage()`, `isStopRequested()`, `notifyState()`, `requestStop()`, and the toolbox's `notifyDataChanged()` — may be called from any thread (e.g. reporting a connection loss from an I/O callback). Everything else stays on the callback thread. 3. **Timestamps are `int64` nanoseconds since the Unix epoch.** Not seconds, not row indices. Extract or synthesize a real absolute nanosecond value. Use `std::chrono::system_clock` for receive-time stamps — **never** `high_resolution_clock`, which is since-boot on some platforms. (Only data with genuinely no time axis — e.g. a CSV with no time column — may fall back to a documented synthetic index-as-time mode.) 4. **`saveConfig()`/`loadConfig()` must be a complete, deterministic round-trip** with no ambient state (no `QSettings`, no cwd assumptions). Persist every field. A DataSource must run `loadConfig(saved) → start()` headless, with no dialog. A parser must decode from its config alone. Dialogs and toolboxes restore from the layout *before data arrives*. Tolerate older configs by migrating or defaulting missing keys. Do not *fail* `loadConfig()` merely because referenced data is not loaded yet. Re-resolve in `onDataChanged()`. 5. **String lifetimes across the ABI.** SDK overrides return `std::string` — the base class buffers it for the ABI, one buffer per slot, invalidated at the next call of the *same* method on the same instance. So never hold the host's view of a previous return, and if you ever hand out a raw `const char*` yourself, it must stay valid until your next call. 6. **Arrow buffers use RAII holders.** Wrap read out-params in `PJ::sdk::ArrowSchemaHolder` / `ArrowArrayHolder`; wrap write streams in `PJ::sdk::ArrowStreamHolder`. On a **successful** append the host takes ownership (the holder disarms); on **failure** you still own it (the holder releases). Never release twice. 7. **Guard host access.** Check `writeHostBound()` / `runtimeHostBound()` (or the toolbox equivalents) before using a host view. 8. **Object-like data → a builtin type + its codec.** Images, point clouds, grids, transforms, markers go through the builtin object vocabulary, not raw scalar fields. See `references/builtin-objects.md`. ## Verify - Builds clean and produces a shared library. - `PJ_*_PLUGIN` macro present exactly once per family; manifest literal has the required keys (+ `encoding` for a parser). - Load it in PlotJuggler, or preferably test the **real built `.so`** through the host loaders (`DataSourceLibrary::load(path)`, `MessageParserLibrary::load(path)`, …). Inject the path as a compile definition. Bind SDK test helpers as host services: `pj_base/sdk/testing/parser_write_recorder.hpp` and `pj_plugins/testing/toolbox_test_store.hpp`. Assert on the writes.
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る