Skip to content

feat(sdk): host surfaces for derived objects and builtin field tables (0.36.0) - #202

Draft
Alvvalencia wants to merge 22 commits into
mainfrom
feat/derived-objects-sdk-integration
Draft

Alvvalencia wants to merge 22 commits into
mainfrom
feat/derived-objects-sdk-integration

Conversation

@Alvvalencia

@Alvvalencia Alvvalencia commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review and merge order: SDK #202 → publish plotjuggler_sdk 0.36.0 → PJ4 #697 → #698 → #699. Plugins after the SDK: #326 (independent) → #327 → #328. Chart preview: PJ4 #702 (independent).

Summary

Draft. It stays in draft until the PJ4 side passes its gate: three stacked PRs to release-4.1 (PlotJuggler/PJ4#697, #698, #699) and the plugins PR (PlotJuggler/pj-official-plugins#325). The release of 0.36.0 follows the merge.

This is the SDK half of derived objects in PlotJuggler 4: scripts (Luau, Python) that compute point clouds, scene entities and image annotations from recorded data, evaluated at the cursor, in batch, or incrementally. 19 commits on top of v0.35.0; size breakdown below. VERSION 0.36.0, CHANGELOG [0.36.0] — Unreleased, floors updated (check_feature_floors.py OK).

Size

Added Removed Files
Production code +2 373 −34 25
Tests +1 980 −18 13
Docs (.md, CHANGELOG) +402 −13 8
Build, config, scripts +23 −1 3
Total +4 778 −66 49

Of the production lines, 1 307 are the field tables (builtin/*_fields.hpp, field_table*.hpp: headers, no ABI) and 778 are plugin_data_api.h / .hpp (the data-processor and plot-tab contract, mostly doc-comments). This PR's own CI can run (the SDK builds itself); the PJ4 and plugins PRs cannot until v0.36.0 is tagged.

What is in it, and what to decide

# Surface ABI impact Question for the reviewer
1 Field tables + registry (builtin/field_table.hpp, *_fields.hpp for 8 types, describe(BuiltinObjectType)) None (headers, client side) Is the FieldKind vocabulary (kOptionalNumber, fixed kList with list_replace, kBuffer with BufferLayout) the contract we want binders to rely on?
2 Typed data-processor requests: create_data_processor_v2, submit_evaluation / poll_evaluation / release_evaluation, PJ_DATA_PROCESSOR_TIME_FLAG_WINDOW/INSTANT Tail slots on PJ_data_processors_host_vtable_t, gated by struct_size; new request/output/budget structs with their own struct_size; layout pinned by sentinels Evaluation handles: is the JSON report of poll_evaluation the right boundary, and is release_evaluation mandatory for every submitted handle?
3 Catalog snapshot v2: acquire_catalog_snapshot_v2 (PJ_catalog_snapshot_v2_t, PJ_object_topic_info_t) Tail slot on PJ_toolbox_host_vtable_t; new struct A new struct + slot because object topics are array elements with a fixed stride. Agreed as the rule for array elements?
4 Scene 3D/2D tabs as kinds of pj.plot_tabs.v1 tabs: tail slots create_tab_v2, attach_topic, detach_topic, focus_tab Tail slots on PJ_plot_tab_host_vtable_t (64 → 96 bytes); the 7 v1 slots and the plot tab_config JSON are unchanged; slots are NULL when the host has no scene workspace One id namespace per plugin across kinds (same id with another kind replaces the tab; same id and kind: a plot tab is replaced by an empty plot, as v1 does, a scene tab only gets its title updated). OK? list_tab_ids does not return the kind, so a client that wants only scene tabs reads each tab_config. Worth a kind-aware listing slot now, or later if a client needs it?
5 ImageAnnotations wire: top-level timestamp and image_topic (field 6) Additive protobuf fields; old readers skip them Not a canonical wire-schema break, so MINOR. Agreed?
6 unprojectPixel for rectified metric depth (depth_image_utils.hpp) None (inline header) Today only PJ4 uses it. Keep it in the SDK for plugins, or move it to PJ4?
7 pj_snapshot object-topic metadata key None —
8 Plugin catalog test: loader environment isolated Tests/CMake only —
9 Output inference: PJ_DATA_PROCESSOR_FLAG_INFER_OUTPUTS; the report carries a root outputs array New request flag (old hosts reject the bit) Naming rule for unnamed returns (value, text, cloud, …) lives in the host. OK as host policy, documented here?
10 struct_size read-prefix rule for PJ_data_processor_request_t and PJ_evaluation_budget_t: hosts read the prefix they know and accept a larger struct; a new field is announced by a new flag bit Rule change (the previous text rejected a larger struct, which would break every newer plugin on a 0.36 host) Agreed as the rule for every future appended field?
11 out_topics for on_demand number outputs = the catalog key of the series the host writes; coverage.error in the poll report; an unknown poll state is an error in pollEvaluation Contract text + C++ wrapper —
12 Capability detection rule, written once: hasTypedRequests(), hasCatalogSnapshotV2(), hasSceneTabs(); flag bits are not probed; Python is probed with validateScript C++ wrappers only —
13 Embedded scene views in plugin dialogs: WidgetData::setSceneView / setSceneTopics / clearSceneView, dialog host bit PJ_DIALOG_HOST_EMBEDS_SCENE_VIEWS (hostHas()) Dialog-protocol keys + one capability bit (no vtable change) —
14 Manifest: optional badge (short label shown next to what a plugin creates) and custom_topics_editor (the toolbox the host opens to create/edit derived topics) Manifest parsing only —
15 PlotTabHostView::createV2/focus renamed createTabV2/focusTab; kDerivedMetadataKey; @since 0.36.0 on every new surface Wrapper rename (unreleased API) —
16 Docs: lifetime of EPHEMERAL / HISTORY_EXEMPT / pinned per kind, per-kind WINDOW/label table, config answers the owner's own ephemerals, chart auto-zoom semantics Docs —

If you prefer smaller PRs

Possible cuts: (a) 1 + 6 + 7 + 8, no ABI; (b) 2 + 3, data-processor and catalog host contract; (c) 4, scene tabs; (d) 5, wire. Each cut needs its own CHANGELOG/VERSION/floors share, and PJ4 needs all of them before it can pin 0.36.0.

Test plan

  • ./build.sh --debug && ./test.sh: 98/99. The only red is abi_check_test (the historical abidiff baseline), red before this branch as well.
  • check_feature_floors.py, test_sdk_install.sh
  • PJ4 built and tested against this branch with --sdk-local
  • CI

Changes after the architecture review (2026-10-05)

  • PJ_data_processor_output_t gains uint64_t reserved[2] (stride 48): the one array element of the typed request that could not grow is now extensible; a host rejects nonzero reserved.
  • PJ_DATA_PROCESSOR_REQUEST_V1_MIN_SIZE / PJ_EVALUATION_BUDGET_V1_MIN_SIZE name the read-prefix minimums (pinned by the ABI sentinels).
  • Header rules: a string value a client does not recognise in a key it knows is an error (outputs[].type "unknown" excepted); evaluation budgets and handle/report limits are host policy (no PJ4 numbers in the contract; coverage.stopped says which budget ended an evaluation); inferred output names are chosen by the host; the v1 "name:type" suffix of create_data_processor is deprecated (no client uses it).
  • The data_processor_config "series" block documents optional done/total (replay progress).
  • hostCapabilities() removed from the docs: it never existed; hostHas() is the API.

Tests: 98/99; the one failure is the historical abi_check_test baseline comparison, red before this branch.

…ocessor contract

Add header-only FieldTable<T> descriptions for PointCloud, FrameTransforms,
SceneEntities and ImageAnnotations with a describe(BuiltinObjectType) registry,
so a generic script binder and a topic describer read one source of truth.
Document kind="on_demand" (typed "name:type" outputs, luau only) and add the
DataProcessorsHostView::createOnDemand shim. Client-side only: no ABI or wire
change, host contract unchanged.
The pj.data_processors.v1 vtable had no layout sentinels, so tail slots
appended to it could drift unnoticed. Pin the seven existing slots and the
fat pointer before the on-demand slots are appended. VERSION moves to 0.35.0
(minor: additions only); the CHANGELOG entry is filled in with the new
surfaces once they exist.
…t v2, typed data-processor requests, evaluation handles, scene views

Tail-appended, struct_size-gated additions (host contract extended, floor 0.35.0):

- PJ_toolbox_host_vtable_t::acquire_catalog_snapshot_v2 returns the scalar
  catalog plus every object topic with dataset, builtin type, entry count and
  raw time range in one deep copy (PJ_catalog_snapshot_v2_t, fixed strides).
- PJ_data_processors_host_vtable_t gains create_data_processor_v2 (typed
  outputs, label, INSTANT pin or WINDOW), and the submit/poll/release
  evaluation triple: an evaluation of an installed on_demand node or of an
  ephemeral recipe, at an instant or over a window under a cooperative budget,
  read back as a JSON report through a per-host handle. The host may complete
  the work inline or in the background; the contract is the same.
- pj.scene_views.v1: plugin-owned 3D/2D views with attach/detach/focus and a
  read-back config, the object counterpart of pj.plot_tabs.v1.
- pj_snapshot object-topic metadata marker for clear-and-replace topics.

C++ views, layout sentinels, fake-host tests, feature floors, snapshot and
docs updated; abidiff shows no additional change over the existing baseline.
Eight builtin types now describe themselves. Two additive descriptor
features were needed: an optional-number kind for the compressed depth
range, and in-place element replacement for fixed-size arrays (camera
matrices), which cannot grow. Raw image buffers expose their pixel record
layout when the encoding is a known raw layout; compressed and video
payloads keep the codec name and no static record size. Client-side only:
no host-contract change.
Client-side helper next to the depth image utilities: a pixel with a
positive metric depth and a conventional pinhole K unprojects to a camera
frame point; singular, non-finite or skewed intrinsics are rejected.
…image_topic

Both are struct fields that never reached the wire, so a canonical or
recorded annotations topic could not say which image it belongs to.
Written only when set, so existing payloads are byte-identical; readers
that predate the fields skip them.
Keep feature floors and metadata coherent with the experimental host contract.
Include the official zero-copy point-cloud codec and its regression coverage.

Validation: 99 ordinary SDK tests passed; historical ABI comparator remains
a known failing gate. Local Conan package and matching consumers built.
Resolved VERSION to 0.36.0 (ours) and CHANGELOG.md by retitling the
[0.36.0] section to Unreleased (dropping the experimental-identity
language) and appending the official [0.35.0] section from the tag
below it.
pj.scene_views.v1 (never released) duplicated pj.plot_tabs.v1: both manage
plugin-owned tabs in the same workspace. Scene 3D/2D tabs become kinds of
plot tabs through four tail slots of PJ_plot_tab_host_vtable_t:
create_tab_v2 (kind plot/3d/2d), attach_topic, detach_topic and focus_tab.
The seven v1 slots, their offsets and the plot tab_config JSON are
unchanged; a host without a scene workspace leaves the tail slots NULL.
PlotTabHostView gains createV2/attachTopic/detachTopic/focus/hasSceneTabs.
Lets a plugin ask whether the host offers the typed request surface
(create_data_processor_v2 and the evaluation handles) before offering
features that need it, instead of probing a call and parsing its error.
A plugin can declare "badge" (e.g. "AI"), a short label the host shows next to
what the plugin created. Absent, the host falls back to the plugin's name.
…eturns

PJ_DATA_PROCESSOR_FLAG_INFER_OUTPUTS lets a transient evaluation run with no
declared outputs: the host names and types each returned value and lists them
under "outputs" in the report. On create, the declared outputs are the ones a
trial run learned. Available since 0.36.0.
WidgetData::setSceneView(name, "3d"|"2d") and setSceneTopics turn a plugin
QFrame into a live scene view bound to object topics; clearSceneView removes
it. Hosts without support leave the frame empty. Available since 0.36.0.
…, unknown poll state is an error

- struct_size of the typed request and the evaluation budget: the host reads
  the prefix it knows and accepts a larger size; a later field is announced
  by a flag bit, never by the size.
- on_demand out_topics returns <owner>/<id>/<name> for number outputs too.
- pollEvaluation reports an unknown state as an error instead of pending.
- document coverage.error, coverage.gaps and the poll limits.
…ics_editor, plot-tab renames

- ToolboxHostView::hasCatalogSnapshotV2 and the capability-detection rule,
  stated once in plugin_data_api.h.
- PJ_DIALOG_HOST_EMBEDS_SCENE_VIEWS (bit 5) plus DialogPluginBase
  hostCapabilities()/hostHas() over the existing set_host_info slot.
- PluginDescriptor::custom_topics_editor manifest flag.
- sdk::kDerivedMetadataKey / kDerivedOnDemandValue.
- PlotTabHostView::createV2 -> createTabV2, focus -> focusTab.
- @SInCE 0.36.0 on every slot, struct and wrapper added on this line.
- floors keys follow the renames; kTabledTypeCount text fixed.
…on probe

Document EPHEMERAL / HISTORY_EXEMPT / pinned lifetime, what each kind does
with label and WINDOW, Python as an optional per-host on_demand language,
plot-tab replace semantics, attach idempotency, the non-atomic snapshot v2,
scene_view limits and the badge fallback. Update the toolbox guide, the
architecture data-processors section, the dialog guide and the plugin skill
reference; fix a stray backtick in plugin_data_api.hpp.
hostHas reads DialogHostInfo::has and hostCapabilities() goes. The on_demand out_topics rule, the read-prefix rule and the Python probe are stated once in plugin_data_api.h; the C++ wrappers point to it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant