Skip to main content
Basis records and replays messages in the MCAP format. The recorder side integrates directly with Publisher — any topic that matches the recorder’s filter is written automatically on every Publish() call. The replayer reads an MCAP file and re-publishes each message through a TransportManager at the correct wall time.
Recorder types live in basis::recorder (aliased into basis::). The replayer lives in basis::. Headers: cpp/recorder/include/basis/recorder.h and cpp/replayer/include/basis/replayer.h.

class OwningSpan

basis/recorder.h A helper that bundles a std::span<const std::byte> with the shared_ptr that owns the underlying allocation, ensuring the data outlives the span.
template <typename T>
OwningSpan(std::shared_ptr<T> ptr, const std::span<const std::byte>& span)
ptr
std::shared_ptr<T>
required
Owning pointer to the object whose memory span views. Kept alive for the lifetime of the OwningSpan.
span
const std::span<const std::byte>&
required
View into the payload bytes. Must point into memory owned by ptr.

Span

const std::span<const std::byte>& Span()
Returns the raw byte span.

class RecorderInterface

basis/recorder.hnamespace basis::recorder Abstract interface implemented by both Recorder and AsyncRecorder. Override this in tests to verify message recording without writing to disk.

Start

virtual bool Start(std::string_view output_name) = 0
Opens the recording destination. For file-based implementations, output_name is used as a filename stem (.mcap is appended).
output_name
std::string_view
required
Name for the recording, without extension.
Returns true on success.

Stop

virtual void Stop() = 0
Flushes and closes the recording. Safe to call multiple times.

RegisterTopic

virtual bool RegisterTopic(
    const std::string& topic,
    const core::serialization::MessageTypeInfo& message_type_info,
    const core::serialization::MessageSchema& basis_schema) = 0
Called once per topic before any messages are written. The implementation may use topic name patterns to decide whether to record this topic.
topic
const std::string&
required
Topic name, e.g. "/camera/rgb".
message_type_info
const core::serialization::MessageTypeInfo&
required
Serializer and type metadata for the topic.
basis_schema
const core::serialization::MessageSchema&
required
Full schema definition (e.g. protobuf descriptor) used for offline decoding.
Returns true if this topic will be recorded; false if it is filtered out. Publisher uses the return value to decide whether to attach the recorder.

WriteMessage

virtual bool WriteMessage(
    const std::string& topic,
    OwningSpan payload,
    const basis::core::MonotonicTime& now) = 0
Called by PublisherBase::PublishRaw() for every published message on a registered topic.
topic
const std::string&
required
The topic the message was published on.
payload
OwningSpan
required
Serialized message bytes, bundled with the owning allocation.
now
const basis::core::MonotonicTime&
required
Monotonic timestamp at the time of publishing.
Returns true on success.

class Recorder

basis/recorder.hnamespace basis::recorder Synchronous (blocking) MCAP recorder. Writes messages inline on the calling thread. Appropriate for low-frequency topics or when latency is acceptable.

RECORD_ALL_TOPICS

static const std::vector<std::pair<std::string, std::regex>> RECORD_ALL_TOPICS
Default topic_patterns value that matches every topic.

Constructor

Recorder(
    const std::filesystem::path& recording_dir = {},
    const std::vector<std::pair<std::string, std::regex>>& topic_patterns = RECORD_ALL_TOPICS)
recording_dir
const std::filesystem::path&
default:"{}"
Directory where MCAP files are written. Defaults to the current working directory.
topic_patterns
const std::vector<std::pair<std::string, std::regex>>&
default:"RECORD_ALL_TOPICS"
Allowlist of (label, regex) pairs. A topic is recorded if its name matches any regex in the list. Use RECORD_ALL_TOPICS to record everything.

Start

virtual bool Start(std::string_view output_name) override
Opens <recording_dir>/<output_name>.mcap for writing using the "basis" MCAP profile.

Stop

virtual void Stop() override
Closes the MCAP writer. Called automatically by the destructor.

Split

bool Split(std::string_view new_name)
Closes the current MCAP file and opens a new one named new_name. Useful for creating time-bounded recordings without stopping and restarting the recorder.
new_name
std::string_view
required
New output filename stem (.mcap is appended automatically).
basis::Recorder recorder("/data/recordings");
recorder.Start("run_001");

// ... time passes ...

recorder.Split("run_002"); // new file, same recorder

class AsyncRecorder

basis/recorder.hnamespace basis::recorder Asynchronous recorder that writes messages on a dedicated background thread. WriteMessage() returns immediately after enqueuing the event, minimizing latency on publisher threads.

Constructor

AsyncRecorder(
    const std::filesystem::path& recording_dir = {},
    const std::vector<std::pair<std::string, std::regex>>& topic_patterns =
        Recorder::RECORD_ALL_TOPICS,
    bool drain_queue_on_stop = true)
recording_dir
const std::filesystem::path&
default:"{}"
Directory for output files.
topic_patterns
const std::vector<std::pair<std::string, std::regex>>&
default:"Recorder::RECORD_ALL_TOPICS"
Topic allowlist, same semantics as Recorder.
drain_queue_on_stop
bool
default:"true"
When true, Stop() blocks until the write queue is fully flushed before closing the file. Set to false to discard pending messages on shutdown.

Start

virtual bool Start(std::string_view output_name)
Opens the underlying Recorder and starts the background write thread.

Stop

virtual void Stop()
Signals the write thread to stop. If drain_queue_on_stop is true, waits for all queued writes to complete before closing the file.

RegisterTopic

virtual bool RegisterTopic(
    const std::string& topic,
    const core::serialization::MessageTypeInfo& message_type_info,
    const core::serialization::MessageSchema& basis_schema)
Thread-safe. Acquires a mutex and delegates to the underlying Recorder::RegisterTopic().

WriteMessage

virtual bool WriteMessage(
    const std::string& topic,
    OwningSpan payload,
    const basis::core::MonotonicTime& now)
Enqueues a RecordEvent onto the internal MPSC queue and returns immediately. The background thread drains the queue and writes to disk.
Prefer AsyncRecorder in production. The synchronous Recorder is simpler for testing and for low-rate topics where blocking is acceptable.
// Attach an async recorder to a unit
auto recorder = std::make_shared<basis::AsyncRecorder>(
    "/data/recordings",
    basis::Recorder::RECORD_ALL_TOPICS,
    /*drain_queue_on_stop=*/true);

recorder->Start("my_unit");
auto* transport_manager = unit.CreateTransportManager(recorder.get());

struct Config

basis/replayer/config.hnamespace basis::replayer Configuration for Replayer.
loop
bool
default:"false"
When true, the replayer restarts from the beginning of the recording after reaching the end.
input
std::filesystem::path
Path to the MCAP file to replay.
basis::replayer::Config config{
    .loop = false,
    .input = "/data/recordings/run_001.mcap"
};

class Replayer

basis/replayer.hnamespace basis Reads an MCAP recording and re-publishes each message through a TransportManager at the correct wall time.

Constructor

Replayer(
    Config config,
    basis::core::transport::TransportManager& transport_manager,
    basis::core::transport::CoordinatorConnector& coordinator_connector)
config
Config
required
Replay configuration (input file path, loop flag).
transport_manager
basis::core::transport::TransportManager&
required
Transport manager used to advertise topics and publish replayed messages.
coordinator_connector
basis::core::transport::CoordinatorConnector&
required
Coordinator connection used to announce publishers to the rest of the system.

Run

virtual bool Run()
Opens the recording, sets up a raw publisher for each channel, and pumps messages in timestamp order. Blocks until the recording ends (or indefinitely when config.loop == true). Returns true on normal completion, false on error.

StartTime

basis::core::MonotonicTime StartTime()
Returns the monotonic timestamp of the first message in the recording.
Only valid after Run() has opened the recording or after LoadRecording() has been called.

EndTime

basis::core::MonotonicTime EndTime()
Returns the monotonic timestamp of the last message in the recording.
basis::replayer::Config config{ .input = "/data/recordings/run_001.mcap" };

auto transport_manager = basis::CreateStandardTransportManager();
auto coordinator = basis::core::transport::CoordinatorConnector::Create();

basis::Replayer replayer(config, *transport_manager, *coordinator);

std::cout << "Recording spans "
          << (replayer.EndTime() - replayer.StartTime()).ToSeconds()
          << " seconds\n";

replayer.Run();

Integration example

// Record all topics to /data/recordings/session_001.mcap
auto recorder = std::make_shared<basis::AsyncRecorder>("/data/recordings");
recorder->Start("session_001");

MyUnit unit("my_unit");
unit.WaitForCoordinatorConnection();
unit.CreateTransportManager(recorder.get());
unit.Initialize();

std::atomic<bool> stop = false;
while (!stop) {
    unit.Update(&stop, basis::core::Duration::FromSeconds(0.02));
}

recorder->Stop(); // flushes write queue

// Later: replay the recording
basis::replayer::Config cfg{ .input = "/data/recordings/session_001.mcap" };
auto tm = basis::CreateStandardTransportManager();
auto cc = basis::core::transport::CoordinatorConnector::Create();
basis::Replayer replayer(cfg, *tm, *cc);
replayer.Run();

Build docs developers (and LLMs) love