Schema File Format

The core of SimpleBuffers is the schema file. This contains all of the data structures that can be serialized. SimpleBuffers schemas are stored in files with the .sb extension and are passed to the compiler for code generation.

Let's look at a simple example:

enum RobotJoint {
    j0 = 0;
    j1 = 1;
    j2 = 2;
    j3 = 3;
    j4 = 4;
    j5 = 5;
}

sequence Init {
    expected_firmware: u32;
}

sequence MoveToEntry {
    joint: RobotJoint;
    angle: f32;
    speed: f32;
}

sequence MoveTo {
    joints: [MoveToEntry];
    stop_smoothly: bool;
}

sequence Request {
    id: u32;
    payload: oneof {
        init: Init;
        moveTo: MoveTo;
    };
}

This schema characterizes some functionality for a robot arm. The main data structure is the Request sequence, which contains an ID and a payload, which takes the form of some other sequence. Before we can fully understand what this means, we have to explain some terminology.

Enums

Enums, like in most programming languages, describe a set of finite values. In SimpleBuffers, enums are backed by unsigned integers. Each enumeration must be explicitly assigned to a unique value. Enumerations do not need to be assigned contiguously, as can be seen in the following example:

enum RobotJoint {
    j0 = 0;
    j1 = 1;
    j2 = 2;
    j3 = 3;
    j4 = 4;
    j5 = 5;
    unknown = 255;
}

The size of the backing integer is determined by the possible enumerations. In the above example, RobotJoint will be backed by an 8-bit integer, as all enumerations can fit in it. However, if unknown's value were changed to be 300 instead of 255, all RobotJoint instances would instead be backed by a 16-bit integer as they no longer fit in 8.

Sequences

Sequences are SimpleBuffers' equivalent to structs. Importantly, sequences are ordered; changing the order of a sequence's fields will cause the serialization format to change. Semicolons are required after every field.

sequence MoveToEntry {
    joint: RobotJoint;
    angle: f32;
    speed: f32;
}

Every field of a sequence (or oneof) must be annotated with a type. A type can be one of the following:

  • Primitive
  • List
  • Enum
  • Sequence
  • Oneof

Primitive Types

SimpleBuffers contains the following primitive types:

TypeDescription
u8An unsigned, 8-bit integer
u16An unsigned, 16-bit integer
u32An unsigned, 32-bit integer
u64An unsigned, 64-bit integer
i8A signed, 8-bit integer
i16A signed, 16-bit integer
i32A signed, 32-bit integer
i64A signed, 64-bit integer
f32A 32-bit floating point
f64A 64-bit floating point
boolA boolean value (8-bit)
strA string

Note that, unlike the rest of the primitive types, strings are variable-sized fields. This entails a small amount of additional overhead which is explained further in Serialization Format.

Lists

Like strings, lists are variable-sized. See Serialization Format for more information about the implications of this.

Lists are denoted by surrounding a type in square brackets. For example:

sequence MoveTo {
    joints: [MoveToEntry];
    stop_smoothly: bool;
}

The joints field is an array of MoveToEntry sequences.

OneOf

Like a union in C, a oneof allows a single field to have multiple possible data types. In our example, Request uses a oneof for the payload field. While the syntax looks similar to a sequence, a oneof can only store a single value at a time.

sequence Request {
    id: u32;
    payload: oneof {
        init: Init;
        moveTo: MoveTo;
    };
}

Multiple oneof fields may be of the same type. This can be useful for readability and clarity, e.g.:

sequence LoginInfo {
    user: oneof {
        email: str;
        phone_num: str;
        username: str;
    };
}

Comments

SimpleBuffers uses C-style single-line comments denoted by //. Multiline comments are not supported.

// I am a comment
sequence MySequence {
    my_field: u8; // This is my field whom I love very much
}