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
آخر نشاط في المصدر
٦ سبتمبر ٢٠٢٦ في ٢٢:٢١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١١
التفرعات
٢

خيارات التثبيت

يُحدَّد 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