Skip to content

feat(sdk): DataSource per-topic pause — advertise + pj.topic_subscription.v1#140

Merged
facontidavide merged 4 commits into
mainfrom
feat/topic-subscription-abi
Jul 6, 2026
Merged

feat(sdk): DataSource per-topic pause — advertise + pj.topic_subscription.v1#140
facontidavide merged 4 commits into
mainfrom
feat/topic-subscription-abi

Conversation

@facontidavide

Copy link
Copy Markdown
Contributor

Problem

Streaming sources today make topic selection a one-shot decision before start(): subscribe to everything (bandwidth/CPU paid for topics nobody looks at) or a narrow set (stop/restart to explore anything else). There is no ABI for a source to advertise its topic universe cheaply and let the host drive which topics actually transmit.

What this adds (strictly additive ABI)

New capabilityPJ_DATA_SOURCE_CAPABILITY_PER_TOPIC_PAUSE (1<<6) + kCapabilityPerTopicPause mirror. Everything below is gated on it; non-supporting sources are untouched.

Plugin → host advertisePJ_available_topic_t (topic_name, parser_encoding, type_name, schema) + a notify_available_topics runtime-host tail slot (vtable grows 96→104, PJ_HAS_TAIL_SLOT-gated), wrapped by DataSourceRuntimeHostView::notifyAvailableTopics. Contract: every call carries the FULL current set (declarative, no deltas), entered on the plugin's poll thread. On an old host the wrapper errors, so a new plugin can detect and fall back to its legacy subscribe-selected-at-start path.

Host → plugin control — new extension "pj.topic_subscription.v1" (PJ_TOPIC_SUBSCRIPTION_EXTENSION_V1): set_active_topics(ctx, names, count, err) — again a declarative full set, entered from the host GUI thread with ctx = handle.context(). Wrapped by DataSourceHandle::setActiveTopics (safe no-op when the extension is absent). The intended plugin implementation is a mutex-protected latest-wins slot drained by onPoll — not a command queue.

Compat discipline — additions only: MIN_VTABLE_SIZE (128), PROTOCOL_VERSION (4), PJ_ABI_VERSION (5) unchanged; ABI layout sentinels updated. No version-source bump yet — recorded under CHANGELOG [Unreleased]; the release number is a bundled, later decision (queued behind #135).

Verification

  • notify_available_topics_test: advertise round-trip, old-host graceful degrade, extension round-trip through handle.context(), handle no-op without the extension. 49/49 SDK tests pass.
  • Contract proven end-to-end by the consumer PRs below, including a protocol-level live E2E (dlopen the real plugin .so + mock host vtable): advertise → default-paused (0 msgs) → set_active_topics → data flows → empty set → flow stops. Verified for both foxglove-bridge (mcap ws player) and ROS 2 (live talker via the production proxy).

Consumers (review together)

🤖 Generated with Claude Code

…tion.v1

Add a backward-compatible (additive) ABI so a streaming source can expose
all its topics cheaply while only transmitting data for topics the host is
actually displaying.

- New capability PJ_DATA_SOURCE_CAPABILITY_PER_TOPIC_PAUSE (1<<6) +
  kCapabilityPerTopicPause mirror.
- Plugin->host advertise: PJ_available_topic_t + notify_available_topics
  runtime-host tail slot, wrapped by
  DataSourceRuntimeHostView::notifyAvailableTopics (PJ_HAS_TAIL_SLOT-gated;
  errors on an old host so a new plugin can fall back).
- Host->plugin control: get_plugin_extension("pj.topic_subscription.v1")
  -> PJ_topic_subscription_v1_t::set_active_topics (declarative full set),
  wrapped by DataSourceHandle::setActiveTopics (passes context() as ctx;
  safe no-op when the extension is absent).
- Fix the stale "same thread as start()" runtime-host doc.

ABI is additions-only: MIN_VTABLE_SIZE (128), PROTOCOL_VERSION (4) and
PJ_ABI_VERSION (5) are unchanged; the runtime-host vtable deliberately
grows 96->104. Sentinels updated. No version-source bump: recorded under
CHANGELOG [Unreleased] (release number is a bundled, later decision).

Tests: notify_available_topics_test (advertise round-trip, old-host
degrade, extension round-trip, handle no-op) + updated helpers; 49/49
SDK tests pass. Docs + capability table updated; pj_base/CLAUDE.md
versioning gotcha reconciled with Release Versioning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
facontidavide and others added 3 commits July 6, 2026 16:23
REQUIREMENTS.md's DataSource capability table was missing
kCapabilityPerTopicPause (data-source-guide.md had it, this table didn't).
ARCHITECTURE.md had no mention of the new notify_available_topics tail slot
or the pj.topic_subscription.v1 extension at all. Also fixes an unrelated
pre-existing stale offset for push_message (documented as 96, actually 88
per the ABI sentinel test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DataSourcePluginBase::bind(), FileSourceBase/StreamSourceBase lifecycle
methods, the DataSourceRuntimeHostView/ToolboxRuntimeHostView non-trivial
methods, ToolboxPluginBase::bind(), and both families' ABI trampolines were
inline in headers with no technical requirement to be (none are templates;
pj_base already builds as a real STATIC lib that every plugin .so links
privately, so these compile once into the archive instead of once per
consumer TU). Declarations stay in the headers; bodies move to matching
pj_base/src/*.cpp files. No behavior change — build clean, 49/49 tests
pass, ABI layout sentinels unaffected.

Templates (vtableWithCreate<CreateFn>, pushMessage<FetchMessageData>) and
trivial one-line getters/forwards are left inline as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The per-topic-pause work on this branch is strictly additive (tail slot +
plugin extension, both struct_size/PJ_HAS_TAIL_SLOT-gated), and the last
publicly tagged release (v0.14.0) already carries PR #135's
pj.data_processors.v1 break -- git history confirms ca052ce is an ancestor
of v0.14.0. So the delta since the last tag is additive-only: a MINOR bump
is correct per CLAUDE.md's versioning rules, not the 1.0.0 the stale
CHANGELOG/conanfile.py comments implied.

Bump all three canonical version sources (conanfile.py, CMakeLists.txt
PJ_PACKAGE_VERSION, recipe.yaml). Also fixes CHANGELOG.md: the
pj.data_processors.v1 entry was still sitting under [Unreleased] with a
"must ship as 1.0.0" note, even though it already shipped in v0.14.0 --
moved it to a backfilled [0.14.0] section with the versioning note
corrected to past tense.

packaging/conan-center-index/** intentionally untouched: it pins the real
published v0.14.0 tag + its sha256 for eventual Conan Center submission,
not one of the three synced version sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@facontidavide facontidavide merged commit 9ee3ee3 into main Jul 6, 2026
6 of 7 checks passed
@facontidavide facontidavide deleted the feat/topic-subscription-abi branch July 6, 2026 17:29
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