Analyze your FlatBuffers data with Foxglove
We’re thrilled to announce that Foxglove now offers FlatBuffers support. FlatBuffers is a cross-platform, data serialization library that compiles data to a strongly typed binary format. It is optimized for transmission, storage, and speed, making it a popular data format for applications where performance is critical – such as gaming, mobile, and robotics.
You can now store your FlatBuffers data in MCAP files, upload these files to Foxglove for analysis and visualization.
FlatBuffers streamlines reading by allowing you to access your serialized data without iterating through it to parse or unpack it. Foxglove leverages these benefits when reading in FlatBuffers-encoded byte arrays, as it uses a slice into the message buffer to access the data it needs.
FlatBuffers is also memory-efficient when writing to MCAP files. While its serialized data output is on par with Protobuf, it has a much smaller memory footprint than JSON.
While using FlatBuffers for your robotics data can provide many of these benefits, choosing a data serialization format is ultimately a choice between tradeoffs that depend on your specific constraints and application. Be sure to check out the official FlatBuffers documentation for performance benchmarks and more information.
In addition to FlatBuffers support in Foxglove, we’ve added FlatBuffers schema files to the @foxglove/schemas
repo. These schema files are available to import or copy into your own projects, so you can read or write FlatBuffers messages that adhere to schemas supported by Foxglove visualizations.
To get started with using these new schemas, let’s walk through how we would use FlatBuffers to write foxglove.Grid
messages to an MCAP file, so we can play it back in Foxglove for debugging and analysis.
First, let’s copy the Grid.fbs
file and its dependencies from @foxglove/schemas
into a project folder. Then, let’s use the FlatBuffers schema compiler to compile our Grid.fbs
file’s dependencies and generate a binary Grid.bfbs
file (“binary FlatBuffers schema”):
$ flatc -b --schema -o <OUTPUT_PATH> <PATH_TO_FBS_FOLDER>
language-bash
The output .bfbs
file makes sure that all the tools needed to decode the schema are self-contained in that file. We would repeat this step of generating .bfbs
files for every schema we want to register to an MCAP channel.
To construct our serialized message buffers, we must also generate TypeScript files from our original Grid.fbs
file:
$ flatc --ts -o <OUTPUT_PATH> <PATH_TO_FBS_FOLDER>
language-bash
We’re finally ready to start writing foxglove.Grid
messages to an MCAP file!
Let’s create a file that imports McapWriter
from the @mcap/core
TypeScript library. We’ll be using this FileHandleWritable class to specify our output MCAP file (flatbuffer.mcap
):
import { McapWriter } from "@mcap/core";
// …
const fileHandle = await open("flatbuffer.mcap", "w");
const fileHandleWritable = new FileHandleWritable(fileHandle);
const mcapFile = new McapWriter({
writable: fileHandleWritable,
useStatistics: false,
useChunks: true,
useChunkIndex: true,
});
language-typescript
Let’s reference our binary FlatBuffers schema file to create a buffer for our messages:
const binaryGridSchemaBuffer = fs.readFileSync(<PATH_TO_GENERATED_GRID_BFBS_FILE>);
language-typescript
Next, we’ll register an MCAP channel with this grid schema:
const gridSchemaId = await mcapFile.registerSchema({
name: "foxglove.Grid",
encoding: "flatbuffer",
data: binaryGridSchemaBuffer,
});
const gridChannelId = await mcapFile.registerChannel({
schemaId: gridSchemaId,
topic: "grid",
messageEncoding: "flatbuffer",
metadata: new Map(),
});
language-typescript
Let’s import the Builder
from the flatbuffers
npm package to write our serialized message data to the buffer – reference the official TypeScript FlatBuffers tutorial for more information on this step.
For our example, we’ll be using a makeNewGrid
helper function to construct a grid object conforming to our Grid
TypeScript definition, and a buildGridMessage
helper function to actually write this object to a buffer:
import { Builder } from "flatbuffers";
// …
const grid = makeNewGrid();
const gridBuilder = new Builder();
const fbGrid = buildGridMessage(gridBuilder, grid as Grid);
language-typescript
We’ll see that buildGridMessage
constructs the message depth-first, as required by FlatBuffers – for example, before it can buildPose
to add the grid’s pose
data to the buffer, it must first buildVector3
for the pose’s position
and buildQuaternion
for the pose’s orientation
to add those primitive values to the buffer. In short, all sub-schema data needs to be added to the buffer before we can call FbPose.start
:
function buildPose(...args) {
const pos = buildVector3(...args);
const quat = buildQuaternion(...args);
FbPose.start(builder); // FbPose is the generated TypeScript class for the `Pose` FlatBuffers schema
// …
}
const pose = buildPose(...args);
language-typescript
After writing all non-primitive fields to the buffer, we start
the Grid
data and include each field’s buffer index in the actual grid message (e.g. addPose
):
const pose = buildPose(builder, json.pose);
// ... build other Grid fields to the buffer
FbGrid.start(builder); // initiate the Grid message
FbGrid.addPose(builder, pose); // Adds pose’s buffer index to the grid message
language-typescript
Once we’ve added all grid fields to the buffer, we need to finish
the builder with our grid message’s index. This makes the Grid our buffer’s root schema – when reading the buffer, our code can rely on the fact that the entire buffer conforms to this schema:
gridBuilder.finish(fbGrid);
language-typescript
We can now add our message data as a UINT8 array it to our registered MCAP channel:
await mcapFile.addMessage({
channelId: gridChannelId,
sequence: 0,
publishTime: //message publish time,
logTime: // message log time,
data: gridBuilder.asUint8Array(),
});
language-typescript
And there we have it! Running this file will write FlatBuffers-encoded foxglove.Grid
messages to an MCAP file. For more details, check out the full example here.
We want to make it easy for all robotics teams – regardless of language or framework – take full advantage of our tools. Now that FlatBuffers have full support across the Foxglove ecosystem, we hope people can start to reap the full benefits of this powerful format and load their data into Foxglove more easily.
To get started with writing FlatBuffers data in different languages, check out the official FlatBuffers tutorial guide here. To explore all Foxglove schemas, you can check out our docs or the @foxglove/schemas GitHub repo. As always, join our Discord community to ask questions, or reach out to us directly with any feedback.
Special thanks to James Kuszmaul for the contributions and help along the way.