Skip to content

Repository files navigation

schema

CI License: AGPL v3

If this work helps you, please support it: Become a supporter

Write your data types once and generate code to read and write them in nine languages.

package example

const MaxHealth = 1000

enum ShipType { Fighter, Corvette, Bomber }

flags ShipFlags { Firing, Thrusting, Disabled }

type Vec3
{
    x float64
    y float64
    z float64
}

type Quaternion
{
    x float64
    y float64
    z float64
    w float64 = 1.0
}

type ShipState
{
    ship_type  ShipType
    ship_flags ShipFlags
    position   Vec3
    rotation   Quaternion
    health     int32 | min = 0, max = MaxHealth
    at_rest    bool
    if !at_rest
    {
        linear_velocity  Vec3
        angular_velocity Vec3
    }
}

This declaration compiles to C, C++, C#, Go, Java, Rust, JavaScript, Dart and Elixir code that reads and writes your data types and agrees on every bit. Now your native plugin, your Unity client, your Go backend, your browser client, and your tooling all speak the same language.

Why it exists

Multiplayer games serialize the same data in several languages at once — an engine client here, a dedicated server there, tools and services around them. Every way of solving that costs something:

  • Hand-written serializers drift. Someone widens a field on one side; the other side keeps reading the old width, and an afternoon goes to a bug that is one bit wide. Separate read and write paths drift too — even within a single language, the reader and the writer are two expressions of one format that nothing forces to agree.
  • A unified read/write function fixes the drift and costs you elsewhere. Templating one function over a read stream and a write stream is a good C++ answer, and it is still slower than the hand-written pair — and it is not available at all in Go, or in most of the languages a game actually has to ship in.
  • General-purpose formats fix the drift by paying for it on the wire. On one representative gameplay packet that is 28 bytes against Cap'n Proto's 52, Protobuf's 56 and FlatBuffers' 72 — every number measured by running the real encoder, reproducible from COMPARISON.md.

Generating the code takes the fourth path. One declaration produces the reader and the writer, in every language, so they cannot disagree — and because the format is decided at compile time, what comes out is the straight-line code you would have hand-written, not an interpreter walking a schema at runtime.

Features

  • One declaration, nine languages — C, C++, C#, Dart, Elixir, Go, Java, Rust and JavaScript, bit-identical on the wire, reader and writer generated together so they cannot drift.
  • Bit-packed, not byte-packed| min = 0, max = 1000 costs 10 bits, not 4 bytes. Bounds are part of the type, and the wire cost follows from them.
  • Branches that cost nothingif !at_rest { … } omits whole field groups from the wire, back-referencing a bool already sent.
  • Compressed floats| min, max, resolution sends a step index, not a float. A 0–1 throttle at 0.01 costs 7 bits.
  • Fixed point is a type in the languagefixed(48, 16) and its unsigned sibling ufixed(48, 16) are declared like any other field, and the compiler owns both the storage and the wire for them.
  • 128-bit integers, ranged like any other, in every target language.
  • Zero allocation, no runtime reflection — straight-line code reading and writing your own buffers.
  • Reads validate, always — in every language. Out-of-range values are refused, not clamped or trusted.
  • Relocatable by construction — generated types are trivially copyable and standard-layout, so raw-struct blobs and parallel scatter/gather are safe by design; see The wire.
  • The compiler is a library too — load, check and generate from Go, and register generators of your own; the nine built-in backends come through the same interface yours does. See Embedding the compiler.
  • Canonical source format — every command formats in place.
  • Generated code is yours, under whatever licence you ship. See License.

How do I use it?

go build -o /usr/local/bin/schema ./cmd/schema

schema check    <dir of .schema files>
schema generate --lang c|cpp|cs|dart|elixir|go|java|js|rust --out <outdir> <dir>

USAGE.md is the guide — every language feature, with real examples and the code each one generates, and how to drive the compiler from Go instead of the command line.

Building the tests needs the serialize runtimes checked out beside this repo (generated Dart, Java and Elixir are self-contained and need only their pinned toolchains), then make testCONTRIBUTING.md has the clone list and what the gates prove.

Documentation

Document What's in it
USAGE.md Every language feature, with the code it generates. Start here.
PERFORMANCE.md Generated-code benchmarks, and how to read them honestly.
SPEC.md The normative reference — grammar, wire law, every edge case.
COMPARISON.md The same packet in schema, Cap'n Proto, Protobuf and FlatBuffers — 28 vs 52 vs 56 vs 72 bytes, measured, with a script to re-run it.
FAQ.md Isn't this just FlatBuffers / Protobuf / Cap'n Proto? And other blunt questions.
VERSIONING.md What a version number promises — chiefly that a 1.x upgrade will not move your wire.
CONTRIBUTING.md How to build it, the gates a change has to pass, and what a golden change means.
SECURITY.md The threat model, and how to report a vulnerability privately.

License

The compiler is AGPL-3.0 — and will stay that way. The code it generates is yours.

  • The schema compiler (everything in this repository) is licensed under the GNU Affero General Public License v3.0, with an explicit additional permission for generated output written into LICENSE itself — not only described here. Every generated file also carries that statement in its own header. If you modify the compiler and run it as a service or distribute it, the AGPL's terms apply to those modifications.
  • Generated code is explicitly NOT covered by the AGPL. The output the compiler produces from YOUR schema files belongs to YOU, under whatever terms you choose — including in closed-source projects. Running the compiler over schemas you own does not make your generated serializers derivative works of the compiler, and this grant is intentional and permanent: schema is meant to be useful to people shipping proprietary software. Only the compiler itself is open source.

Author

Glenn Fiedler and Rowan Claude, Más Bandwidth LLC.

About

The schema language: bitpacked serialization schemas compiled to C, C++, C#, Dart, Elixir, Go, Java, JavaScript and Rust

Resources

Contributing

Security policy

Stars

39 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages