Skip to main content
Synchronizers decide when a multi-input handler should fire. Each synchronizer holds per-input storage and exposes OnMessage<INDEX>() and ConsumeIfReady(). The concrete policy (All, FieldSync, …) implements IsReadyNoLock() to define the firing condition.
All types live in the basis::synchronizers namespace. Headers are in cpp/synchronizers/include/basis/synchronizers/.

MessageSumType

using MessageSumType = std::tuple<T_MSG_CONTAINERs...>;
The type of the tuple consumed by a synchronizer and passed to the handler. Each element corresponds to one input declared on the handler, in declaration order. Each element is a container type, which can be:
Container formMeaning
std::shared_ptr<const T>Single most-recent message
std::vector<std::shared_ptr<const T>>Buffered messages
std::variant<std::monostate, std::shared_ptr<const T>, std::shared_ptr<const U>>Message or alternate inproc type
// Handler receiving one Image and one CameraInfo
using MessageSumType = std::tuple<
    std::shared_ptr<const sensor_msgs::Image>,
    std::shared_ptr<const sensor_msgs::CameraInfo>
>;

MessageMetadata<T_MSG_CONTAINER>

synchronizer_base.h Per-input metadata that modifies synchronizer behavior.
is_optional
bool
default:"false"
When true, this input does not block the ready check. The handler fires even if no message has been received on this input.
is_cached
bool
default:"false"
When true, consuming the synchronizer output does not clear this input’s storage. The last received message is retained and re-used on subsequent firings.

SynchronizerBase<T_MSG_CONTAINERs...>

synchronizer_base.h Abstract CRTP-free base for all synchronizers. Manages per-input Storage objects and a mutex protecting them.

Template parameters

T_MSG_CONTAINERs...
typename...
One type per handler input, in order. Each is the container type for that input (see MessageSumType above).

Constructor

SynchronizerBase(MessageMetadata<T_MSG_CONTAINERs>&&... metadatas)
SynchronizerBase(std::tuple<MessageMetadata<T_MSG_CONTAINERs>...>&& metadatas = {})
metadatas
MessageMetadata per input
Per-input metadata. Pass as individual arguments or as a tuple. When omitted, all inputs use default metadata (required, not cached).

OnMessage<INDEX>

template <size_t INDEX>
bool OnMessage(auto msg, MessageSumType* out = nullptr)
Stores msg in the slot for input INDEX, then checks if the synchronizer is ready.
INDEX
size_t
required
Zero-based index of the input. Must be within [0, sizeof...(T_MSG_CONTAINERs)).
msg
auto
required
The received message. Must be assignable to the container type at position INDEX.
out
MessageSumType*
default:"nullptr"
When non-null and the synchronizer becomes ready, the consumed MessageSumType is written here while the lock is still held. Avoids a separate ConsumeIfReady() call.
Returns true if the synchronizer is ready after storing the message.

ConsumeIfReady

std::optional<MessageSumType> ConsumeIfReady()
Thread-safe. Returns the current MessageSumType and clears non-cached inputs if ready, or std::nullopt otherwise.

IsReady

bool IsReady()
Returns true if the synchronizer would fire right now, without consuming the messages.

All<T_MSG_CONTAINERs...>

all.h — inherits SynchronizerBase Fires when every non-optional input has received at least one message. The simplest and most common synchronizer policy.
template <typename... T_MSG_CONTAINERs>
class All : public SynchronizerBase<T_MSG_CONTAINERs...>

Ready condition

IsReadyNoLock() returns true when AreAllNonOptionalFieldsFilledNoLock() is true — i.e. every input with is_optional == false holds a non-null message.
// Fires once both /image and /camera_info have been received
basis::synchronizers::All<
    std::shared_ptr<const sensor_msgs::Image>,
    std::shared_ptr<const sensor_msgs::CameraInfo>
> sync;

sync.OnMessage<0>(image_msg);
if (auto msgs = sync.OnMessage<1>(info_msg, &msgs_out)) {
    auto [image, info] = msgs_out;
    // process
}

Field<T_CONTAINER_MSG, T_FIELD>

field.h A compile-time descriptor that associates a container type with a field accessor. Used as a template argument to FieldSync to declare which field to synchronize across messages.
template <typename T_CONTAINER_MSG, auto T_FIELD>
struct Field {
  using ContainerMessageType = T_CONTAINER_MSG;
  static constexpr auto FieldPtr = T_FIELD;
};
T_CONTAINER_MSG
typename
required
The container type, e.g. std::shared_ptr<const sensor_msgs::Image>.
T_FIELD
auto
required
A pointer to member or a lambda/function pointer that extracts the sync key from the underlying message. Pass nullptr to mark an input as unsynced (it acts like All for that slot).
// Sync on a protobuf timestamp field accessed via a method
using ImageField = basis::synchronizers::Field<
    std::shared_ptr<const sensor_msgs::Image>,
    [](const sensor_msgs::Image* img) { return img->header().stamp(); }
>;

// Sync on a direct member pointer
using LocalizationField = basis::synchronizers::Field<
    std::shared_ptr<const Localization>,
    &Localization::stamp
>;

// Unsynced input — always uses the latest value
using MapField = basis::synchronizers::Field<
    std::shared_ptr<const MapData>,
    nullptr
>;

FieldSync<T_OPERATOR, T_FIELD_SYNCs...>

field.h — inherits SynchronizerBase Synchronizes messages across inputs by comparing extracted field values using T_OPERATOR. When a new message arrives on a synced input, the synchronizer searches all other synced inputs’ buffers for a matching field value.
template <typename T_OPERATOR, typename... T_FIELD_SYNCs>
  requires internal::NoContainerSupportForFieldSync<T_FIELD_SYNCs...>
class FieldSync : public SynchronizerBase<typename T_FIELD_SYNCs::ContainerMessageType...>
T_OPERATOR
typename
required
A struct with a templated operator()(const T1& t1, const T2& t2) that returns a truthy value when two field values are considered “in sync”. See Equal and ApproximatelyEqual below.
T_FIELD_SYNCs...
typename...
One or more Field<> descriptors. Inputs whose FieldPtr == nullptr are unsynced and always use the most recently received message.
FieldSync does not support vector (buffered) container types for synced inputs. Unsynced (nullptr) inputs may use any container type.

OnMessage<INDEX> (overridden)

template <size_t INDEX>
bool OnMessage(auto msg, MessageSumType* out = nullptr)
For synced inputs: buffers msg, extracts its field value, and searches all other synced input buffers for matching values. For unsynced inputs: directly stores msg (overwrites). Returns true when all non-optional inputs are filled with a matched set.

FieldSyncEqual<T_FIELD_SYNCs...>

field.h Alias for FieldSync<Equal, T_FIELD_SYNCs...>. Fires when field values are exactly equal (==).
template <typename... T_FIELD_SYNCs>
using FieldSyncEqual = FieldSync<Equal, T_FIELD_SYNCs...>;
basis::synchronizers::FieldSyncEqual<
    basis::synchronizers::Field<
        std::shared_ptr<const LidarScan>,
        [](const LidarScan* s) { return s->header().stamp(); }>,
    basis::synchronizers::Field<
        std::shared_ptr<const Localization>,
        &Localization::stamp>
> sync;

FieldSyncApproximatelyEqual<EPSILON, T_FIELD_SYNCs...>

field.h Alias for FieldSync<ApproximatelyEqual<EPSILON>, T_FIELD_SYNCs...>. Fires when field values satisfy std::abs(a - b) <= EPSILON.
template <auto EPSILON, typename... T_FIELD_SYNCs>
using FieldSyncApproximatelyEqual = FieldSync<ApproximatelyEqual<EPSILON>, T_FIELD_SYNCs...>;
EPSILON
auto (non-type template parameter)
required
Maximum allowed difference between two field values. Must be a compile-time constant of a numeric type compatible with std::abs and operator- on the field’s value type.
// Allow timestamps within 5ms of each other
constexpr int64_t kToleranceNs = 5'000'000;

basis::synchronizers::FieldSyncApproximatelyEqual<
    kToleranceNs,
    basis::synchronizers::Field<
        std::shared_ptr<const sensor_msgs::Image>,
        [](const sensor_msgs::Image* img) { return img->header().stamp(); }>,
    basis::synchronizers::Field<
        std::shared_ptr<const sensor_msgs::PointCloud2>,
        [](const sensor_msgs::PointCloud2* pc) { return pc->header().stamp(); }>
> sync;

Custom operator example

You can define your own T_OPERATOR for domain-specific matching:
struct SequenceEqual {
    template <typename T1, typename T2>
    bool operator()(const T1& a, const T2& b) {
        return a.sequence_id == b.sequence_id;
    }
};

using MySynchronizer = basis::synchronizers::FieldSync<
    SequenceEqual,
    basis::synchronizers::Field<
        std::shared_ptr<const FrameA>, &FrameA::seq>,
    basis::synchronizers::Field<
        std::shared_ptr<const FrameB>, &FrameB::seq>
>;

Type utilities

These helpers are used internally and in generated code.

HasPushBack<T>

A C++20 concept. Satisfied when T has a push_back(value_type) method — i.e. when the container is a std::vector.

ExtractFromContainer<T>

Extracts the element type from a container:
  • For vector-like types: T::value_type
  • Otherwise: T itself

IsStdVariant<T>

Type trait that is std::true_type when T is a std::variant specialization.

Build docs developers (and LLMs) love