The Unit class hierarchy
Basis provides two base classes you can derive from directly when writing Units by hand, or that the code generator derives from on your behalf.basis::Unit
Unit is the base class for all Basis units. It holds the transport manager, coordinator connector, and the registered handler map. It provides the core lifecycle methods:
basis::SingleThreadedUnit
SingleThreadedUnit extends Unit with a shared callback queue. All handler callbacks are serialized through a single SubscriberOverallQueue, which means your handler code does not need locks — only one handler runs at a time.
SingleThreadedUnit is the only threading model currently available. A multi-threaded variant where handlers run in parallel is planned but not yet implemented.Unit lifecycle
Every Unit executable follows the same four-step startup sequence:Connect to the coordinator
The coordinator is a separate process that tracks which topics are published and by whom. If the coordinator is not running you will see:
WaitForCoordinatorConnection() blocks and retries every second until the coordinator is reachable.Create the transport manager
CreateTransportManager() instantiates the inproc transport and any registered network transports (TCP by default when using CreateStandardTransportManager). Each Unit gets its own transport manager and therefore its own publisher ID space.RecorderInterface* to record all messages published by this unit to an MCAP file.Initialize
Initialize() is where you create publishers, subscribers, and rate subscribers. It is called exactly once, after the transport manager exists.Initialize, call Advertise<T>() and Subscribe<T>() to set up your topics:Run the update loop
Update() is called repeatedly. It drives the transport layer (connects to new publishers as reported by the coordinator) and, for SingleThreadedUnit, drains the callback queue.max_execution_duration argument caps how long Update() blocks waiting for an event. If you pass 0, it returns immediately after processing any pending events.HandlerPubSub — the generated handler structure
When you use code generation (via .unit.yaml), Basis generates a HandlerPubSub subclass for each declared handler. You rarely interact with this class directly, but understanding it helps when reading generated code or debugging.
HandlerPubSub is a base struct that wires together a synchronizer, a set of typed subscriber callbacks, and a publish step:
HandlerPubSubWithOptions<T_DERIVED, HAS_RATE, INPUT_COUNT> adds:
SetupInput<INDEX>()— creates aSubscriber<T_MSG>via the transport manager and registers the inbound callback.SetupInputs()— iterates over all inputs using index sequences and callsSetupInputfor each.CreateOnMessageCallback<INDEX>()— builds the typed lambda that callssynchronizer->OnMessage<INDEX>()and, if consumed, callsRunHandlerAndPublish().OnRateSubscriber()— called by the rate subscriber thread; callssynchronizer->ConsumeIfReady()and publishes if ready.
Unit class registers each handler’s HandlerPubSub* in the handlers map, which is used by the DeterministicReplayer and UnitManager.
Writing a Unit by hand
You can subclassSingleThreadedUnit directly without code generation. The basis_example.cpp example demonstrates this pattern:
Units with code generation
For production use, declare your Unit in a.unit.yaml file and run the Basis code generator. The generator produces a typed Base class with all the HandlerPubSub subclasses wired up automatically. You implement only the handler functions.
example.unit.yaml
Base class handles Initialize() entirely. Your derived class overrides only the handler methods:
Key properties
Unit name
Unit name
Each Unit is constructed with a string name passed to the
Unit base constructor. The name is used as the logger name (via spdlog) and is surfaced in coordinator telemetry. Choose a name that is unique within a process.Topic name templating
Topic name templating
Units declared in YAML support topic name templates such as
{{args.camera_name}}/rgb. The templated_topic_to_runtime_topic map in Unit resolves templates to their runtime values before subscribers are created. This lets you parameterize topic names without recompiling.UnitInitializeOptions
UnitInitializeOptions
Initialize() accepts a UnitInitializeOptions struct. Currently it has a single field:create_subscribers = false during deterministic replay to suppress subscriber creation and replay messages directly through the type-erased callback map instead.Next steps
Pub-sub messaging
How publishers and subscribers work, including inproc zero-copy and network transport
Synchronizers
Control when handlers fire: all, equal, approximate, rate, or external
Unit YAML schema
Full reference for the
.unit.yaml declaration formatCode generation
What
generate_unit.py produces and how to use it in CMake