Get SpatialOS

Sites

Menu

Schemalang reference

Directory structure

You define the schema for a simulated world in the schema subdirectory of the project, using ‘schema files’ (files with the .schema extension).

Running spatial worker codegen generates corresponding API code for each package, component, and data type in the schema.

Package definition

Each schema file has a package definition at the top (eg package foo.bar;). You can arrange the files in arbitrary directory structures in the schema directory. For example, you could put the code for the package foo.bar in the directory schema/foo/bar.schema.

Comments

Schemalang supports both // line comments and /* */ block comments.

Components

Define components using the component keyword. Each component must have:

  • an explicit component ID
  • at least one field (each with a type, a name, and an explicit field ID)

Components can also contain events.

An example component

package improbable.example;

component Health {
  id = 1234;
  uint32 current_health = 1;
  uint32 max_health = 2;
}

IDs

Component IDs must be unique across the entire schema (including library dependencies with their own schemas). Field IDs must be unique within the component.

These two types of IDs are essential for schema evolution: they let you change the schema without breaking existing snapshots.

Component IDs below 100 and from 190000 to 199999 are reserved for Improbable use.

Types

Primitive types

The primitive types available are:

Syntax Type Notes
bool Boolean True or false.
uint32, uint64 Unsigned integer Variable-length encoding; smaller values use fewer bits.
int32, int64 Signed integer Variable-length encoding; smaller values use fewer bits. Negative values are represented in the usual two’s-complement manner, and so use the maximum number of bits.
sint32, sint64 Zig-zag signed integer Variable-length zig-zag encoding; smaller absolute values use fewer bits. More space-efficient than int32 or int64 when values are likely to be negative.
fixed32, fixed64, sfixed32, sfixed64 Fixed-width integer Fixed-width encoding (always 4 or 8 bytes depending on type); more space-efficient when values are likely to be very large.
float, double Floating-point
string, bytes String of characters or bytes Strings should always be either ASCII or UTF-8.
EntityId ID of an entity A special version of int64 used to store the ID of a SpatialOS entity. IDs are > 0.

User-defined types

You can define and reuse custom types, using the type keyword. Custom types consist of field definitions which look exactly the same as in components.

You can also define types within the scope of an outer type, to make the types to similary nested in generated code. Refer to nested types from elsewhere by writing out the path (perhaps relative to the current scope) with dots as separators.

Here is an example:

package improbable.example;

import "improbable/vector3.schema";

type Movement {
  Vector3d target_position = 1;
  Vector3d target_direction = 2;
}

type Foo {
  type Nested {
    int32 nested_int = 1;
  }

  int32 foo_int = 1;
  double foo_double = 2;
}

type Bar {
  type Nested {}

  Foo foo = 1;
  // Resolves to Bar.Nested
  Nested bar_nested = 2;
  // Resolves to Foo.Nested
  Foo.Nested foo_nested = 3;
}

Again, field IDs must be unique within the type.

Enumerations

You can define enumerations and use them like built-in types. Like types, enums can be defined within the scope of an outer type.

Example:

package improbable.example;

enum Color {
  RED = 0;
  GREEN = 1;
  BLUE = 2;
}

type ColorData {
  // enum Color could be defined inside this scope, too!
  Color color = 1;
}

Collection types

The collection types available are:

  • An option<T> represents either no value (empty) or a single T value.
  • A list<T> represents zero or more T values.
  • A map<K, V> represents a map from keys of type K to values of type V.

There is no restriction on what kind of values can be stored in options or lists. However, the keys to a map can only be an integral type, enumeration type, string or EntityId.

Example:

package improbable.example;

type SomeDataType {
  option<int32> an_integer = 1;
  list<SomeDataType> more_data = 2;
  map<EntityId, EntityId> entity_to_entity_map = 3;
}

You can’t create nested collections (like lists of lists, maps of lists, lists of options, and so on) directly. Use wrapper types instead:

package improbable.example;

type Data {
  type InnerList {
    list<int32> value = 1;
  }
  list<InnerList> list_of_list = 1;
}

Component events

Events, unlike other component fields, are not persistent: the simulated world doesn’t hang on to the data. They let an entity broadcast a message about something that has happened to it.

Define the events associated with a component using the event T name; syntax. For example:

package improbable.example;

type SwitchToggled {
  int64 time = 1;
}

component Switch {
  id = 1234;
  bool is_enabled = 1;
  event SwitchToggled toggled;
}

The above example allows the component to trigger a toggled event that contains a time field. Other parts of the simulated world can react to this event.

Unlike regular component fields, an event field is not persistent (the simulation does not save the data). In all other respects, events work the same way as updates to persistent fields. It allows an entity to broadcast a transient message to other workers with the entity in their local view of the simulation.

The above definition would allow the component to trigger a toggled event containing a time field, which other parts of the simulation can react to.

Component commands

A component can also define commands, which facilitate communication in the other direction: whereas updates and events are issued by the authoritative worker for a particular component and delivered to other workers that can see the entity, commands allow any worker to send a request to the authoritative worker. The authoritative worker can take action and should respond to the request.

Commands are defined using the command keyword as follows:

package improbable.example;

type DamageRequest {
  uint32 amount = 1;
}

type DamageResponse {}

component Health {
  id = 1234;
  uint32 health = 1;
  command DamageResponse damage(DamageRequest);
}

As shown, commands require both a request and a response type. The request type is the data that is sent to the authoritative worker, and the response type is sent back to the worker that issued the command.

Importing types

Schema files can use types defined in other schema files by importing the files. For example, if a file called foo/bar.schema contains the following:

package foo.bar;

type Baz {}

Another schema file can use the type Baz like this:

package improbable.example;
import "foo/bar.schema";

component BazComponent {
  id = 1234;
  foo.bar.Baz baz = 1;
}

The path of the file is relative to the schema directory (either the schema directory of the SpatialOS project, or the schema directory of some other library dependency).

To avoid ambiguity when working with packages sharing similar names, you can prefix references to user-defined types with a dot, to indicate a fully-qualified name (including package). For example, to refer to Baz above, you could write .foo.bar.Baz.

Naming

Field names (including events and commands) must be in lowercase_with_underscores. Names of types (components, user-defined types and enumerations) must be in UpperCamelCase.

This is to allow generated code to compile correctly in all target languages, no matter what names are used. The naming rules are designed to avoid naming conflicts in the generated code.

For example, if there could be two fields named foo_bar and fooBar, it’s not obvious how to name the accessors for these fields in (for example) Java while conforming to Java’s conventions.

Enforcing a consistent naming style avoids this problem, reduces confusion, and makes the schema definition clearer.

Advanced components

Reusable data types

To allow multiple components to share a data type, instead of defining a field inline, components can reference an external user-defined type for a field. Use the syntax data T; as follows:

package improbable.example;

type SomeData {
  int32 value = 1;
}

component SomeComponent {
  id = 1234;
  data SomeData;
}

component AnotherComponent {
  id = 1235;
  data SomeData;
}

You can’t mix this syntax with in-line field definition, or combine multiple data types this way. For example, the following definitions are not valid and won’t compile:

package improbable.example;

type SomeData {
  int32 value = 1;
}

component ThisComponentWontCompile {
  id = 1234;
  data SomeData;
  int32 extra_field = 2;
}

type OtherData {
  int32 value = 3;
}

component ThisComponentAlsoWontCompile {
  id = 1337;
  data SomeData;
  data OtherData;
}

Syntax highlighting plugins

There are several community projects for schema syntax highlighting:

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums