Retire the TF service, tf is a standard topic, add IO[T] ports - #3169
Conversation
…orts The magic lazily-attached self.tf service (PubSubTF/LCMTF/ZenohTF with its own pubsub stack per module) implied a second protocol next to module streams: invisible to blueprints, io(), recorders and remapping. tf is now an ordinary TFMessage stream named "tf", wired like any other port. - core: new IO[T]/RemoteIO bidirectional stream kind. Ports declared as tf: IO[TFMessage] publish and subscribe on one topic; BlueprintAtom records direction "inout". The coordinator needed no changes: same-named ports already share one transport, so N writers + M readers on /tf is native pub/sub. - protocol/tf: TFSpec/TFConfig/PubSubTF/LCMTF/ZenohTF deleted, along with tf_backend() and ModuleConfig.tf_transport. What remains is MultiTBuffer (+ receive_tfmessage/get_pose) and TF — a disposable buffer view over any stream-like (IO/In port, raw Transport, or nothing for a local buffer). - modules: publishers declare tf: Out[TFMessage] and publish TFMessage; lookup users declare tf: In[TFMessage] and build self._tf = TF(self.tf) in start(); Detection2DModule uses IO for both. Recorder records its own tf In port instead of introspecting the service's pubsub. StreamTF is a plain class. Wire format is unchanged (/tf on LCM, dimos/tf on zenoh), so recordings and external listeners are unaffected. End-to-end coverage in test_io_port_autoconnects_and_flows_both_ways: shared topic, IO both directions, loopback.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Greptile SummaryThe PR replaces the implicit TF service with ordinary typed stream ports and adds bidirectional
Confidence Score: 5/5The PR appears safe to merge, with no remaining blocking failure related to the prior TF subscription-cleanup thread. The module shutdown path now disposes the conventional TF view before stopping its port transports, removing its subscription while the transport remains active. Important Files Changed
Sequence DiagramsequenceDiagram
participant Module
participant TFView as TF Buffer
participant Port as TF In/IO Port
participant Transport
Module->>TFView: first tfbuffer access
TFView->>Port: subscribe(receive_tfmessage)
Port->>Transport: subscribe callback
Transport-->>TFView: TFMessage
Module->>TFView: dispose during shutdown
TFView->>Transport: unsubscribe callback
Module->>Port: stop
Port->>Transport: stop
Reviews (9): Last reviewed commit: "Merge branch 'main' into ivan/feat/tf_si..." | Re-trigger Greptile |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #3169 +/- ##
==========================================
+ Coverage 75.20% 75.23% +0.02%
==========================================
Files 1128 1128
Lines 107954 108040 +86
Branches 9751 9748 -3
==========================================
+ Hits 81188 81282 +94
+ Misses 23955 23952 -3
+ Partials 2811 2806 -5
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 3 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Restores the old ergonomics without the old magic: the port stays an explicit, blueprint-visible declaration; only the buffer view is implicit. Built on first touch (raises clearly without a tf port or transport), disposed with the module, backed by the _tf slot so tests can still inject fakes. Drops the per-module `_tf = TF(self.tf)` boilerplate and None guards across all lookup modules.
Co-authored-by: Paul Nechifor <paul@nechifor.net>
…cation # Conflicts: # dimos/core/test_transport_factory.py # dimos/core/transport_factory.py
Main's #3176 replaced the pubsub-based ZenohRPC with a native queryable implementation that has no topicgen; keys are built as dimos/rpc/<name>, so the no-leading-slash assertion is now structural.
…cation # Conflicts: # dimos/manipulation/manipulation_module.py
What
Kills the magic lazily-attached
self.tfservice. TF is now an ordinaryTFMessagestream namedtf, declared and wired like any other module port. Adds the missing primitive that made this possible: a bidirectionalIO[T]stream kind.Why
the TF service (
PubSubTF/LCMTF/ZenohTF, auto-created on firstself.tftouch) implied a second protocol living next to module streams, its own pubsub stack per module, invisible to blueprints,io(), recorders, remapping and namespacing. Every use is just "subscribe to a tf topic" or "publish on it" - those are pubsub functionssimplifies native modules Retire the TF service, tf is a standard topic, add IO[T] ports #3169
recorder can easily record tf without special code
How
IO[T]ports (core/stream.py): publish and subscribe on the same topic; an IO port sees the whole topic including its own publishes (transport loopback).BlueprintAtomrecords direction"inout".API:
Outside modules,
TF(stream)works over any stream-like — a port, a rawTransport(TF(make_transport("/tf", TFMessage))outside modules), or nothing (TF(), a purely local buffer for tests).dispose()unsubscribes.Module pattern:
tf: Out[TFMessage],self.tf.publish(TFMessage(...))tf: In[TFMessage], lookups viaself.tfbuffertf: IO[TFMessage](Detection2DModule)Deleted:
TFSpec,TFConfig,PubSubTF,LCMTF,ZenohTF,tf_backend(),ModuleConfig.tf_transport, the lazyself.tfproperty. ~30 call sites migrated; the memory2Recorderrecords through its owntf: Inport;StreamTFbecame a plain class; docs rewritten.Compat: wire format unchanged (
/tfLCM,dimos/tfzenoh) - recordings and external listeners unaffected. Namespacing prefixestflike any stream (per-robot trees, consistent withframe_id_prefix).tfis no longer a reserved Module attribute, which unblocks the pure-modules T14 bridge.