Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ Fixes:
- The Conan package now propagates `PJ_ASSERT_THROWS` to consumers when built with
`assert_throws=True`; consumers compiled `PJ_ASSERT` as `assert` against archives built to throw.

The direct C++ parser bridge (docs only, no code change):

- The docs said SDK 1.0 removes the bridge through which a host casts a parser's context to
`MessageParserPluginBase*`. It does not: PlotJuggler 4 reads objects that way for every parser.
The bridge is now documented as deprecated but supported through 1.x, with the rule that follows
from it (`pj_plugins/docs/ARCHITECTURE.md`, section 0a): the `MessageParserPluginBase` prefix,
`sdk::ObjectRecord`, `sdk::BuiltinObject`, `PJ::Expected` and every builtin object struct are
frozen layouts, and a member change in any of them is a MAJOR. A 1.x release lifts the freeze once
the host reads objects through `pj.parser_functional.v2` only.
- 1.0.0 itself changes two of those layouts: `sdk::Log` (`category`) and `sdk::DepthImage`
(`frame_id`). A parser built against 0.x that returns either type must be rebuilt for a 1.0 host.
No parser in pj-official-plugins returns them.

## [0.40.0]

Host contract: extended: pj.mcp_endpoint.v1, PJ_mcp_endpoint_host_vtable_t::acquire_endpoint,
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ Propose a release only according to plugin impact:
Tail-append optional slots and gate them by `struct_size`; keep minimum vtable
sizes frozen. `abidiff` must show additions only for a MINOR.

A member change in a builtin object struct (`pj_base/builtin/`) is a MAJOR while
PlotJuggler reads parser objects through the direct C++ bridge; `abidiff` does
not see it. See [ABI evolution rules](pj_plugins/docs/ARCHITECTURE.md#0a-abi-stability-and-evolution-rules-v5).

The SDK version is **build identity only** — it names what a plugin compiled
against, never what host it runs on. A release that only adds client-side
helpers (static-library code compiled into the plugin) does NOT change runtime
Expand Down
8 changes: 8 additions & 0 deletions docs/builtin_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ frozen eligible bulk field as a splice into the input payload; the host still
reconstructs a canonical SDK object. The schema and wire-format details stay
private; public SDK headers expose only SDK structs.

**A struct member is binary interface for now.** PlotJuggler 4 does not use that
boundary yet: it reads the C++ struct a parser returns directly. Until it moves
to functional v2, adding, removing or reordering a member of a builtin struct
breaks a parser and a host built from different SDK releases, and is released
as a MAJOR. A new builtin type, or a proto field the struct does not expose, is
unaffected. See
[the direct C++ parser bridge](../pj_plugins/docs/ARCHITECTURE.md#0a-abi-stability-and-evolution-rules-v5).

## Serialization Families

Builtin objects fall into two serialization families:
Expand Down
9 changes: 5 additions & 4 deletions pj_base/include/pj_base/message_parser_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
* extension declared in parser_functional_protocol.h. Scalar fields cross
* as synchronous borrowed C values; canonical objects cross as a stable type
* tag plus canonical wire bytes or a v2 splice. No C++ parser class or STL
* value crosses the new path. Parsers built before SDK 0.22 do not expose the
* extension and may use a deprecated host compatibility bridge until the next
* SDK major version. Pure-C plugins may continue using parse() to write
* scalars to writeHost.
* value crosses the new path. A host that has not adopted the extension reads
* objects through the direct C++ bridge instead, which freezes the C++ layouts
* it shares with the plugin: see "The direct C++ parser bridge" in
* pj_plugins/docs/ARCHITECTURE.md, section 0a. Pure-C plugins may continue
* using parse() to write scalars to writeHost.
*
* The host obtains the plugin's vtable via `PJ_get_message_parser_vtable()`
* and drives the plugin through: create -> bind(registry) ->
Expand Down
5 changes: 3 additions & 2 deletions pj_base/include/pj_base/plugin_data_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ extern "C" {
* pre-v5 parser DSOs because the C++ MessageParserPluginBase
* pure-functional contract changed parseScalars()/parseObject() return
* types to ScalarRecord/ObjectRecord. SDK 0.21 subsequently placed those
* results behind an additive C extension, now `pj.parser_functional.v2`; direct
* host calls on the C++ base remain only as a temporary old-plugin bridge.
* results behind an additive C extension, now `pj.parser_functional.v2`. Direct
* host calls on the C++ base remain supported through 1.x as a deprecated
* bridge (pj_plugins/docs/ARCHITECTURE.md, section 0a).
*
* The C data-plane layout remains the v4 layout:
* - Arrow C Data Interface replaces Arrow IPC bytes at the boundary
Expand Down
33 changes: 29 additions & 4 deletions pj_plugins/docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@ diffs the extracted host-surface inventory against the checked-in snapshot),
and the installed copy of the table is what downstream plugin repositories
validate `min_sdk_required` against.

**The direct C++ parser bridge is a second, temporary ABI.** PlotJuggler 4
reads objects from a MessageParser by casting the plugin's opaque context to
`MessageParserPluginBase*` and calling `parseObject()`. It does this for every
parser, not only for the pre-0.22 ones that lack `pj.parser_functional.v2`.
Until the host reads objects through v2 only, these C++ layouts cross the
plugin boundary and are frozen like a C struct:

- the member prefix of `MessageParserPluginBase` and the order of its virtual
functions (members do not move, additions are tail-only);
- `sdk::ObjectRecord`, `sdk::BuiltinObject` and `PJ::Expected`;
- every builtin object struct under `pj_base/builtin/` (`sdk::Image`,
`sdk::PointCloud`, ...), including the structs nested in them.

Adding, removing, reordering or retyping a member of any of them breaks a
parser and a host built from different SDK releases. Nothing detects it:
`PJ_ABI_VERSION` covers the C structs, and the `abidiff` gate checks a
DataSource canary. Such a change is therefore released as a MAJOR. A new
builtin type is not affected (it adds a struct; it changes none), and neither
is a new proto field that the C++ struct does not expose.

The freeze ends in a 1.x release, once the host no longer casts. That release
states it in its changelog. Lifting it changes nothing for a plugin's sources
or binary, except that a parser built before SDK 0.22, which has no v2
extension, can no longer emit objects.

1. **Boot-level ABI symbol.** Every plugin .so exports
`pj_plugin_abi_version` as a `uint32_t` symbol independent of any
vtable. The host `dlsym`s it BEFORE fetching the family vtable;
Expand Down Expand Up @@ -837,10 +862,10 @@ when the separate type tag is known. Concrete builtins and codecs live under
`pj_base/builtin/`; see `docs/builtin_type.md` for the catalog.

Parsers built before SDK 0.22 expose no usable functional extension: pre-0.21
builds expose none, and 0.21 builds expose only the retired v1. The host keeps a
clearly isolated, deprecated direct-C++ bridge for those binaries. New plugins
and new host code use v2; SDK 1.0 can remove the bridge and the frozen
`MessageParserPluginBase` layout constraint.
builds expose none, and 0.21 builds expose only the retired v1. New plugins and
new host code use v2. PlotJuggler 4 still reads objects through the direct C++
bridge for every parser; section 0a states what that freezes and when the
freeze ends.

## Per-topic pause (demand-driven subscription)

Expand Down
10 changes: 7 additions & 3 deletions pj_plugins/docs/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,13 @@ Independent decoder, typically driven by a DataSource via the host.
parser that still overrides only legacy `parse()` must not advertise the
functional extension. Hosts query after `bind_schema` and do not cache an
earlier absence.
- Until SDK 1.0, a host may use an isolated deprecated direct-C++ bridge for a
pre-0.22 parser, which exposes no v2 extension. The bridge and its
class-layout constraint are removed together at 1.0.
- A host may read objects through the deprecated direct-C++ bridge (a cast of
the parser context to `MessageParserPluginBase*`) instead of v2. The bridge
is supported through 1.x. While any supported host uses it, the layouts it
shares with the plugin are frozen: the `MessageParserPluginBase` member
prefix and virtual order, `sdk::ObjectRecord`, `sdk::BuiltinObject`,
`PJ::Expected` and every builtin object struct. A 1.x release lifts the
freeze once the host reads objects through v2 only.

### Dialog

Expand Down
15 changes: 12 additions & 3 deletions pj_plugins/docs/message-parser-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,9 +683,18 @@ Do not bypass it with direct C++ calls.

Parsers built before SDK 0.22 expose no usable functional extension: pre-0.21
builds expose none, and 0.21 builds expose only the retired v1. New host code
negotiates v2 and may use a deprecated, isolated direct-C++ bridge only when v2
is absent. It must not otherwise cast an opaque parser context; SDK 1.0 removes
the bridge.
negotiates v2.

PlotJuggler 4 has not made that move: it reads objects from every parser by
casting the opaque context to `MessageParserPluginBase*` and calling
`parseObject()`. That direct C++ bridge is deprecated but supported through
1.x. For a parser author it has one consequence: a parser and the host must
agree on the layout of the builtin struct the parser returns, so a parser that
emits a type whose struct changed in an SDK release has to be rebuilt against
that release. The SDK treats such a change as a MAJOR for exactly that reason
(see [ABI rules](ARCHITECTURE.md#0a-abi-stability-and-evolution-rules-v5)).
Nothing in a parser's source depends on the bridge, and nothing changes for
it when the host moves to v2.

Builtin types and their canonical codecs live under `pj_base/builtin/`, one
header per type. `serializeBuiltinObject()` / `deserializeBuiltinObject()`
Expand Down
5 changes: 3 additions & 2 deletions pj_plugins/include/pj_plugins/host/message_parser_handle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,9 @@ class MessageParserHandle {
}

/// True when this parser exposes the pure-functional C extension (v2, SDK
/// 0.22 and later). Older parsers return false and can be handled through the deprecated
/// in-process C++ compatibility bridge until the next SDK major version.
/// 0.22 and later). Older parsers return false; a host can only read their
/// objects through the deprecated in-process C++ bridge
/// (pj_plugins/docs/ARCHITECTURE.md, section 0a).
[[nodiscard]] bool supportsFunctionalParsing() const {
return functionalExtension() != nullptr;
}
Expand Down
17 changes: 10 additions & 7 deletions pj_plugins/include/pj_plugins/sdk/message_parser_plugin_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,16 @@ struct MessageParserPluginBaseLayoutSentinel;
/**
* Base class for MessageParser plugins (protocol v4).
*
* TRANSITIONAL ABI LAYOUT CONTRACT: hosts use the
* `pj.parser_functional.v2` C extension and never cast a newly-built plugin's
* opaque context to this class. Hosts may retain a deprecated direct-C++
* bridge only for pre-0.22 plugins that do not expose the extension. Therefore
* the existing member prefix remains frozen within PJ_ABI_VERSION 5: members
* must not move, and additions are tail-only. SDK 1.0 may remove that bridge
* and this layout constraint together.
* ABI LAYOUT CONTRACT (the direct C++ parser bridge): the intended host path
* is the `pj.parser_functional.v2` C extension, which shares no C++ type with
* the plugin. PlotJuggler 4 does not use it yet: it casts every parser's
* opaque context to this class and calls parseObject(). While a host does
* that, this class is binary interface: the existing member prefix and the
* order of its virtual functions are frozen (members must not move, additions
* are tail-only), and so are the layouts of sdk::ObjectRecord,
* sdk::BuiltinObject, PJ::Expected and every builtin object struct. A 1.x
* release lifts the constraint once the host has moved to v2. Full rule:
* pj_plugins/docs/ARCHITECTURE.md, section 0a.
*/
class MessageParserPluginBase {
public:
Expand Down
Loading