Skip to content

Commit 1000ae5

Browse files
Merge plotjuggler/main into internal_main (sync 2026-06-01)
2 parents e0c3cad + 12805ba commit 1000ae5

28 files changed

Lines changed: 3631 additions & 7683 deletions

.github/workflows/linux-ci.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ jobs:
143143
cache: 'true'
144144

145145
- name: Install system packages
146-
run: sudo apt-get update && sudo apt-get install -y xvfb ccache ninja-build zstd
146+
# abigail-tools provides abidw/abidiff for the ABI drift gate (below).
147+
run: sudo apt-get update && sudo apt-get install -y xvfb ccache ninja-build zstd abigail-tools
147148

148149
- name: Configure ccache
149150
# Compiler-output cache for our own C++ — complementary to the Conan
@@ -208,10 +209,19 @@ jobs:
208209
-DCMAKE_C_COMPILER_LAUNCHER=ccache
209210
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
210211
-DPJ_BUILD_PARQUET_IMPORT_EXAMPLE=OFF
212+
-DPJ_ENABLE_ABI_CHECK=ON
211213
212214
- name: Build
213215
run: cmake --build build
214216

217+
- name: ABI drift gate
218+
# Mechanically enforces the Release Versioning policy (CLAUDE.md): a non-MAJOR
219+
# change must not break ABI. Diffs the mock_data_source_plugin canary DSO against
220+
# the checked-in pj_base/abi/baseline.abi via libabigail and FAILS on an
221+
# ABI-INCOMPATIBLE change (bit 8). Backward-compatible additions (bit 4) pass with
222+
# a "baseline may be stale" nudge — refresh via the abi_update_baseline target.
223+
run: cmake --build build --target abi_check
224+
215225
- name: ccache stats
216226
if: always()
217227
run: ccache -s

CLAUDE.md

Lines changed: 89 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,65 @@
22

33
## Project Overview
44

5-
PlotJuggler Core — C++20 foundation libraries for PlotJuggler storage, plugin SDKs, and host-side plugin loading.
5+
PlotJuggler Core — C++20 foundation libraries for PlotJuggler storage, plugin SDKs, and host-side
6+
plugin loading. **Read-only submodule** inside PJ4: consumed as-is; changes happen in this repo,
7+
not in the PJ4 superproject. This file is the single navigation node for the whole submodule — the
8+
three modules below have no own CLAUDE.md.
69

710
### Modules
811

9-
- **pj_base** — foundational SDK/ABI types, canonical builtin object vocabulary, plugin ABI headers,
10-
host-view helpers, ImageAnnotations codec, and shared utilities
11-
- **pj_datastore** — columnar storage engine + `ObjectStore` (for media blobs) + `DerivedEngine` (fmt, tsl::robin_map, nanoarrow)
12-
- **pj_plugins** — C++ plugin SDK base classes, plugin discovery, host-side loaders, config envelope
13-
helpers, and dialog protocol primitives; four plugin families: DataSource, MessageParser, Dialog, Toolbox
12+
- **pj_base** — vocabulary types (`Timestamp`, `DatasetId`, `Expected<T>`, `Span<T>`, type trees),
13+
the canonical builtin object vocabulary (`pj_base/builtin/`: 15 struct headers — Image, DepthImage,
14+
PointCloud, CompressedPointCloud, OccupancyGrid(+Update), Mesh3D, VideoFrame, AssetVideo,
15+
SceneEntities, RobotDescription, CameraInfo, Log, ImageAnnotations, FrameTransforms) and their 14
16+
wire codecs (RobotDescription carries source text as-is — no codec), the C-ABI protocol headers for
17+
DataSource/MessageParser/Toolbox + the C++ SDK base classes / host-view helpers built on them.
18+
- **pj_datastore** — columnar storage engine (`DataEngine`) + `ObjectStore` (media/opaque blobs) +
19+
`DerivedEngine` (fmt, tsl::robin_map, nanoarrow). Plugin-data host implementations live here.
20+
- **pj_plugins** — host-side loaders + RAII handles + plugin discovery/catalog for four plugin
21+
families (DataSource, MessageParser, Dialog, Toolbox), config-envelope helpers, and the **dialog
22+
C ABI** (`pj_plugins/dialog_protocol/`). Note the split: the DataSource/MessageParser/Toolbox C-ABI
23+
protocol headers live in `pj_base`; the **Dialog** protocol header lives here, not in `pj_base`.
1424

1525
### Dependency graph
1626

17-
- `pj_datastore``pj_base`
18-
- `pj_plugins``pj_base`
27+
- `pj_datastore``pj_base` (+ fmt, nanoarrow)
28+
- `pj_plugins``pj_base` (+ nlohmann/json)
1929

20-
## Documentation Workflow
21-
22-
Coding agents should use this context hierarchy:
30+
## Read path
2331

2432
```text
25-
CLAUDE.md -> relevant docs -> code
33+
this CLAUDE.md -> relevant docs -> headers -> code
2634
```
2735

28-
- Start with this file to identify the module and the relevant source-of-truth documents.
29-
- Read the relevant docs before treating code as authoritative for intent. Code shows current implementation
30-
details; docs define the intended architecture, public contracts, terminology, and module boundaries.
31-
- If docs and code disagree, treat that as a documentation consistency problem. Do not silently let stale docs survive.
32-
- Any change to behavior, public APIs, ABI structs, SDK types, module ownership, plugin workflows,
33-
storage formats, or user-facing semantics must include a documentation check.
34-
- Before any commit, verify that documentation is up to date for the change. If docs are stale and the user did
35-
not explicitly ask for a docs update, ask whether to update the docs before committing. Do not commit
36-
known-stale documentation.
36+
Start here to pick the module + source-of-truth doc. Read the doc before treating code as
37+
authoritative for *intent*: code shows current implementation; docs define intended architecture,
38+
public contracts, terminology, and module boundaries. If docs and code disagree, that is a
39+
documentation bug — do not silently let stale docs survive. Any change to behavior, public APIs,
40+
ABI structs, SDK types, module ownership, plugin workflows, or storage formats must include a
41+
documentation check before commit.
3742

3843
## Key Documentation
3944

40-
**Project-wide:**
45+
**Project-wide** (`docs/`):
4146

4247
| Document | Content |
4348
|----------|---------|
44-
| `docs/builtin_type.md` | Canonical builtin object types used as the shim between third-party schemas and PlotJuggler internals |
45-
| `docs/image_annotations_format.md` | Canonical `PJ.ImageAnnotations` wire format for builtin `ImageAnnotations` payloads |
49+
| `docs/builtin_type.md` | Canonical builtin object types — the shim between third-party schemas and PJ internals; lists every builtin + its codec |
50+
| `docs/image_annotations_format.md` | Canonical `PJ.ImageAnnotations` wire format |
51+
| `docs/dialog-sdk-reference.md` | Quick reference for `WidgetData` setters + `DialogPluginTyped` event handlers |
4652
| `docs/cpp_design_recommendations.md` | C++ style, error handling, API design guidelines |
47-
| `docs/toolbox-porting-gap-analysis.md` | SDK gaps identified when porting PJ3 toolboxes (being addressed) |
48-
49-
**Datastore** (`pj_datastore/docs/`):
50-
51-
| Document | Content |
52-
|----------|---------|
53-
| `REQUIREMENTS.md` | Goals, data model, ingest contract, schema evolution (dynamic columns, variable-length sequences), query, derived series |
54-
| `ARCHITECTURE.md` | Internals: domain model, layers, data flow, encoding (constant, FoR, dictionary, packed bool), DerivedEngine |
55-
| `USER_GUIDE.md` | Plugin author's guide: write patterns (named vs bound vs Arrow IPC), read API, pitfalls, ValueRef, TypedNull |
56-
| `OBJECT_STORE_DESIGN.md` | ObjectStore: lazy-fetch media blobs, retention, concurrent access |
53+
| `docs/toolbox-porting-gap-analysis.md` | Historical PJ3→PJ4 toolbox SDK gap analysis (most gaps now closed; read as context, not current reference) |
54+
| `V4_STORE.md` | ObjectStore plugin ABI: services, ownership rules, lazy fetch |
5755

58-
**Plugin system** (`pj_plugins/docs/`):
56+
**Datastore** (`pj_datastore/docs/`): `REQUIREMENTS.md` (data model, ingest contract, schema
57+
evolution, query) · `ARCHITECTURE.md` (domain model, layers, encoding, DerivedEngine) ·
58+
`USER_GUIDE.md` (plugin-author write/read patterns, ValueRef, TypedNull) ·
59+
`OBJECT_STORE_DESIGN.md` (lazy-fetch blobs, retention).
5960

60-
| Document | Content |
61-
|----------|---------|
62-
| `REQUIREMENTS.md` | Plugin families (DataSource, MessageParser, Dialog, Toolbox), interaction model, capability system, config contract |
63-
| `ARCHITECTURE.md` | C ABI protocols, SDK base classes, host loaders, RAII handles, dialog protocol, config envelope |
64-
| `data-source-guide.md` | SDK tutorial: FileSourceBase, StreamSourceBase, delegated ingest, dialog integration |
65-
| `message-parser-guide.md` | SDK tutorial: parse(), schema binding, dialog integration for parsers |
66-
| `dialog-plugin-guide.md` | SDK tutorial: WidgetData, typed events, EmbedUi, requestAccept, onTick |
67-
| `toolbox-guide.md` | SDK tutorial: read+write access, catalog, notifyDataChanged |
61+
**Plugin system** (`pj_plugins/docs/`): `REQUIREMENTS.md` (families, capability system, config
62+
contract) · `ARCHITECTURE.md` (C ABI protocols, SDK base classes, host loaders, dialog protocol) ·
63+
`data-source-guide.md` · `message-parser-guide.md` · `dialog-plugin-guide.md` · `toolbox-guide.md`.
6864

6965
## Build & Test
7066

@@ -74,53 +70,61 @@ CLAUDE.md -> relevant docs -> code
7470
./test.sh # runs tests in all discovered build dirs
7571
```
7672

77-
Dependencies: Conan (`conanfile.txt`).
78-
79-
## Pre-commit Validation
80-
81-
Before committing, first check whether the code changes require documentation updates. If documentation is stale
82-
and the requested task did not include updating it, ask the user whether to update the docs before committing.
83-
84-
Before committing, always run:
85-
86-
```bash
87-
./build.sh --debug && ./test.sh
88-
```
89-
90-
Code formatting and linting are enforced via pre-commit hooks (clang-format v17).
73+
Dependencies come from Conan (`conanfile.py`). Before committing always run
74+
`./build.sh --debug && ./test.sh`. Formatting/linting (clang-format, pinned to v22.1.0 in `.pre-commit-config.yaml`) is enforced by
75+
pre-commit hooks. Verify docs match reality before any commit that changes behavior, public APIs, ABI structs,
76+
SDK types, or storage formats; if stale and not asked to update, ask before committing.
9177

9278
## Release Versioning
9379

94-
In **every PR**, proactively raise whether it warrants a new Conan release, and
95-
propose the version bump rather than waiting to be asked. Pre-1.0 versioning
96-
convention (`0.MINOR.PATCH`):
80+
The version is a **plugin-compatibility contract** (plugins pin this SDK by Conan range), not
81+
decoration. In every PR, proactively raise whether a release is warranted and propose the bump.
82+
The bump is decided by **plugin impact**, semver-style:
83+
84+
- **MAJOR** (`X.0.0`) — an **ABI or API break**: an existing plugin must be recompiled or its
85+
source changed to keep working. Removing/reordering ABI vtable slots, changing a struct layout or
86+
an existing function signature, bumping a `PJ_*_PROTOCOL_VERSION`, or changing a canonical builtin
87+
object schema / `proto` wire format. **`abi/baseline.abi` changes only on a MAJOR.**
88+
- **MINOR** (`x.Y.0`) — a **backward-compatible API addition**: a new capability is added, but every
89+
already-built plugin keeps working **with no recompile**. New entry points are tail-appended and
90+
the host only calls slots an old plugin actually provides (gated by `struct_size`), so an old
91+
`.so` loaded into a newer host is unaffected — it simply ignores what it does not use. `abidiff`
92+
against the baseline must show **additions only**.
93+
- **PATCH** (`x.y.Z`) — backward-compatible **bug fixes** to installed headers/behavior. Changes
94+
invisible to consumers (docs, CLAUDE.md, comments, tests, internal `.cpp` that does not alter an
95+
installed header) take **no bump**.
96+
97+
**Plugin compatibility range.** A plugin built and tested on `X.Y.Z` works on every later
98+
MINOR/PATCH up to the next MAJOR, so it pins `plotjuggler_core/[>=X.Y.Z <(X+1).0.0]` — e.g. built on
99+
`1.4.2``[>=1.4.2 <2.0.0]`. The lower bound is the version that introduced the newest feature the
100+
plugin actually uses (a plugin that does not adopt `0.6`'s additions stays at `>=0.5.2`); the upper
101+
bound is the next MAJOR. Write the range **explicitly** — do not rely on caret/tilde shorthand.
102+
103+
**While pre-1.0 (currently `0.y.z`).** Same rules, with the major being `0`: there are **no breaking
104+
changes within `0.x`** — the next ABI/API break ships as `1.0.0`. So a plugin built on `0.Y.Z` pins
105+
`[>=0.Y.Z <1.0.0]`. (Deliberately stricter than the usual "0.x may break" convention, because
106+
plugins pin against this SDK.)
107+
108+
**Mechanics.** The version lives in two places that must stay in sync — `version` in `conanfile.py`
109+
and `PJ_PACKAGE_VERSION` in the root `CMakeLists.txt` (also update the example tag in the
110+
`conanfile.py` docstring). A non-MAJOR PR must not alter `abi/baseline.abi` beyond additions (verify
111+
with `abidiff`). Tagging and pushing a release is a separate, explicitly-authorized step — never tag
112+
or push a release without the user's go-ahead.
97113

98-
- **MINOR** bump (`0.X.0`) — any API or ABI **break**: removing/reordering ABI
99-
vtable slots, changing existing struct layouts or function signatures, or any
100-
source-incompatible SDK change.
101-
- **PATCH** bump (`0.x.Y`) — **backward-compatible** changes: tail-appended ABI
102-
slots (gated by `struct_size`), additive SDK helpers, bug fixes, docs.
114+
## Coding Conventions
103115

104-
The version is declared in two places that **must stay in sync**: `version` in
105-
`conanfile.py` and `PJ_PACKAGE_VERSION` in the root `CMakeLists.txt` (also update
106-
the example tag in the `conanfile.py` docstring). Tagging and pushing the release
107-
is a separate, explicitly-authorized step — never tag or push a release without
108-
the user's go-ahead.
116+
- **Formatting:** Google style via `.clang-format` — 2-space indent, 120-char limit.
117+
- **Naming:** `CamelCase` classes, `camelBack` functions, `lower_case` variables, `lower_case_`
118+
members, `kCamelCase` constants.
119+
- **Namespaces:** flat `PJ`; `PJ::encoding` and `PJ::arrow_import` for internals.
120+
- **Errors:** `PJ::Expected<T>` for fallible ops, `PJ_ASSERT(cond, msg)` for invariants.
121+
- **Warnings:** `-Wall -Wextra -Werror` on all targets.
109122

110123
## Instructions Glossary
111124

112-
- **"Read all documentation"** means: find and read every `.md` file in the entire project tree (all subdirectories). Use `find . -name "*.md"` or equivalent. This includes docs in `docs/`, `pj_datastore/docs/`, `pj_plugins/docs/`, and any other location.
113-
114-
- **"Update the documentation"** means: based on what you learned during this session, correct any documentation that is outdated or inaccurate, and clarify any ambiguity that caused confusion or errors. If a doc says one thing but the code does another, fix the doc to match reality. If missing information led to a bug, add it.
115-
116-
- **"Check documentation"** means: review the docs listed above that are related to the changed module or API.
117-
Confirm they still describe the current intent and behavior. If not, update them or ask the user before
118-
committing.
119-
120-
## Coding Conventions
121-
122-
- **Formatting:** Google style via `.clang-format` — 2-space indent, 120-char limit
123-
- **Naming:** `CamelCase` classes, `camelBack` functions, `lower_case` variables, `lower_case_` members, `kCamelCase` constants
124-
- **Namespaces:** flat `PJ` namespace; `PJ::encoding` and `PJ::arrow_import` for internals
125-
- **Errors:** `PJ::Expected<T>` for fallible ops, `PJ_ASSERT(cond, msg)` for invariants
126-
- **Warnings:** `-Wall -Wextra -Werror` on all targets; pre-commit hooks enforce clang-format v17
125+
- **"Read all documentation"** — read every `.md` in the tree (`find . -name '*.md'`), including
126+
`docs/`, `pj_datastore/docs/`, `pj_plugins/docs/`.
127+
- **"Update the documentation"** — correct any doc made outdated/inaccurate this session; if a doc
128+
disagrees with code, fix the doc to match reality; add info whose absence caused a bug.
129+
- **"Check documentation"** — review the docs related to the changed module/API; confirm they still
130+
describe current intent and behavior, else update or ask before committing.

cmake/PjAbiCheck.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ add_custom_target(abi_update_baseline
6161
--drop-private-types
6262
--no-show-locs
6363
$<TARGET_FILE:${_pj_abi_canary_target}>
64-
-o ${_pj_abi_baseline}
64+
--out-file ${_pj_abi_baseline}
6565
DEPENDS ${_pj_abi_canary_target}
6666
COMMENT "Regenerating pj_base/abi/baseline.abi (intentional ABI change — review the diff)"
6767
VERBATIM

pj_base/CLAUDE.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# pj_base — SDK vocabulary, builtin object schemas, and the C plugin ABI
2+
3+
pj_base is the **Level 0** foundation and the **SDK boundary** for plugin authors. It owns: the zero-dependency vocabulary types (`Timestamp`, `DatasetId`, `Range`, `Expected<T>`, `Span`, `TypeTree`); the canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `FrameTransforms`, … — 15 types) **and 14 of their wire codecs** (RobotDescription has none); and the **C ABI** primitives every plugin family speaks (`plugin_data_api.h` + the service registry) plus the C-ABI protocol headers for **three** families — `data_source_protocol.h`, `message_parser_protocol.h`, `toolbox_protocol.h`. The **Dialog** protocol header is the exception: it lives in `pj_plugins/dialog_protocol/`, not here. It also ships the C++ SDK base classes for DataSource and Toolbox; the MessageParser and Dialog base classes live in `pj_plugins`. Builds as a STATIC lib with **zero public deps** — `fast_float` is a `BUILD_INTERFACE` private impl detail of `parseNumber`. Must NOT depend on `pj_datastore`, `pj_plugins`, Qt, or any Conan runtime lib. This is a read-only submodule subtree: change it only when explicitly working in `plotjuggler_core`.
4+
5+
## Layout
6+
- `include/pj_base/` — vocabulary primitives: `types.hpp`, `type_tree.hpp`, `dataset.hpp`, `expected.hpp`, `span.hpp`, `number_parse.hpp`, `assert.hpp`, `diagnostic_sink.hpp`, `buffer_anchor.hpp`.
7+
- `include/pj_base/builtin/` — the 15 builtin object struct headers (`*.hpp`; 16 enum values in `BuiltinObjectType`, value 2 reserved) + their 14 wire codecs (`*_codec.hpp`; RobotDescription has none) + the `BuiltinObject` (`std::any`) type-erased holder.
8+
- `include/pj_base/sdk/` — C++ SDK over the ABI: DataSource + Toolbox `*_plugin_base.hpp`, `service_registry.hpp`/`service_traits.hpp`, host views, Arrow RAII holders, `testing/`.
9+
- `include/pj_base/*_protocol.h`, `plugin_data_api.h`, `builtin_object_abi.h`, `plugin_abi_export.hpp` — the stable C-ABI surface for DataSource/MessageParser/Toolbox (the Dialog protocol header lives in `pj_plugins/dialog_protocol/`).
10+
- `proto/pj/` — canonical `.proto` wire contracts for the builtin types (see its README).
11+
- `src/`, `tests/` — codec/parse impls and gtests.
12+
- `abi/baseline.abi` — golden libabigail dump; the ABI-stability regression baseline.
13+
14+
## Gotchas
15+
- ABI numbering is **frozen**: `BuiltinObjectType` (builtin_object.hpp) and `PJ_builtin_object_type_t` (builtin_object_abi.h) share stable numeric values — never renumber; type 2 is permanently reserved. Append-only.
16+
- Every vtable slot is `PJ_NOEXCEPT`; a throw across the ABI boundary calls `std::terminate`. See the header block in `plugin_data_api.h`.
17+
- `BuiltinObject` is `std::any`, deliberately not `std::variant`, for forward-compat — recover via `std::any_cast<T>` / `sdk::typeOf`. See `builtin/builtin_object.hpp`.
18+
- ABI/struct-layout or signature changes require a Conan **MINOR** bump and a refreshed `abi/baseline.abi`; tail-appended slots are a PATCH (see submodule CLAUDE.md → Release Versioning).
19+
20+
## Read deeper
21+
| For | Read |
22+
|---|---|
23+
| Builtin type design, serialization families, type-erasure rules | `../docs/builtin_type.md` |
24+
| ImageAnnotations canonical wire format | `../docs/image_annotations_format.md` |
25+
| C++ style / error-handling (`Expected`, `PJ_ASSERT`) | `../docs/cpp_design_recommendations.md` |
26+
| Per-family ABI driving order + thread tags | `include/pj_base/data_source_protocol.h`, `message_parser_protocol.h`, `toolbox_protocol.h` |
27+
| Writing a DataSource plugin | `include/pj_base/sdk/data_source_patterns.hpp` (start here) |
28+
| Plugin families overview, host loaders | `../pj_plugins/docs/` |
29+
| Wire `.proto` contracts | `proto/pj/README.md` |

0 commit comments

Comments
 (0)