Skip to content
 
 

Repository files navigation

Build Status Coverage Status Hex.pm Documentation


Flatbuffer

This is a flatbuffers implementation in Elixir.

In contrast to existing implementations there is no need to compile code from a schema. Instead, data and schemas are processed dynamically at runtime, offering greater flexibility.

Using Flatbuffer

Schema file:

table Root {
  foreground:Color;
  background:Color;
}

table Color {
  red:   ubyte;
  green: ubyte;
  blue:  ubyte;
}
root_type Root;

Parsing the schema:

iex(1)> {:ok, schema} = Flatbuffer.Schema.from_file("Example.fbs")
{:ok,
 %Flatbuffer.Schema{
   entities: %{
     "Color" => {:table,
      %{
        fields: {
          {:red, {:ubyte, %{default: 0}}},
          {:green, {:ubyte, %{default: 0}}},
          {:blue, {:ubyte, %{default: 0}}}
        },
        field_ids: %{
          "red" => 0,
          "green" => 1,
          "blue" => 2
        }
      }},
     "Root" => {:table,
      %{
        fields: {
          {:foreground, {:table, %{name: "Color"}}},
          {:background, {:table, %{name: "Color"}}}
        },
        field_ids: %{
          "foreground" => 0,
          "background" => 1
        }
      }}
   },
   root_type: {:table, %{name: "Root"}},
   id: nil,
   safe: false
 }}

Serializing data:

iex(2)> color_scheme = %{foreground: %{red: 128, green: 20, blue: 255}, background: %{red: 0, green: 100, blue: 128}}
iex(3)> color_scheme_fb = Flatbuffer.to_binary(color_scheme, schema)
<<16, 0, 0, 0, 0, 0, 0, 0, 8, 0, 12, 0, 4, 0, 8, 0, 8, 0, 0, 0, 18, 0, 0,
  0, 31, 0, 0, 0, 10, 0, 7, 0, 4, 0, 5, 0, 6, 0, 10, 0, 0, 0, 128, 20,
  255, 10, 0, 6, 0, 0, ...>>

So we can read the whole thing which converts it back into a map:

iex(4)> Flatbuffer.read!(color_scheme_fb, schema)
%{
  foreground: %{blue: 255, green: 20, red: 128},
  background: %{blue: 128, green: 100, red: 0}
}

Or we can get a value from the buffer without decoding the whole thing. This can be done either with an atom key (for root-table fields) or with a key-path composed of atoms and vector indices. Binary field names are also accepted:

iex(5)> Flatbuffer.get(color_scheme_fb, [:background], schema)
%{blue: 128, green: 100, red: 0}
iex(6)> Flatbuffer.get(color_scheme_fb, [:background, :green], schema)
100

Safe schemas

By default, schema-defined field, struct, and enum names are converted to atoms once when the schema is built, preserving the library's atom-keyed API. Because atoms are not garbage-collected, use safe: true for untrusted or dynamically varying schemas:

iex> {:ok, safe_schema} = Flatbuffer.Schema.from_file("Example.fbs", safe: true)
iex> Flatbuffer.read!(color_scheme_fb, safe_schema)
%{
  "foreground" => %{"blue" => 255, "green" => 20, "red" => 128},
  "background" => %{"blue" => 128, "green" => 100, "red" => 0}
}

Safe schemas keep decoded field, struct, and enum names as binaries and never intern schema-controlled names as atoms.

Conveniences:

For schemas that will be often used or that need to be included with an application, you can use Flatbuffer.use/1 to compile the schema into a module:

defmodule ColorScheme do
  use Flatbuffer,
    path: "priv/fb",
    file: "color_scheme.fbs"
end

The schema (and any includes) will be read and parsed, and then compiled into the module. The source files for the schema do not need to be read again or included with the application. The functions of Flatbuffer are available in the module, but with the schema predefined.

For example:

iex> color_scheme = %{foreground: %{red: 128, green: 20, blue: 255}, background: %{red: 0, green: 100, blue: 128}}
iex(2)> color_scheme_fb = ColorScheme.to_binary(color_scheme)
<<16, 0, 0, 0, 0, 0, 0, 0, 8, 0, 12, 0, 4, 0, 8, 0, 8, 0, 0, 0, 18, 0, 0,
  0, 31, 0, 0, 0, 10, 0, 7, 0, 4, 0, 5, 0, 6, 0, 10, 0, 0, 0, 128, 20,
  255, 10, 0, 6, 0, 0, ...>>

We can get a value from the buffer without decoding the whole thing:

iex(3)> ColorScheme.get(color_scheme_fb, :background)
%{blue: 128, green: 100, red: 0}
iex(4)> ColorScheme.get(color_scheme_fb, [:background, :green])
100

use Flatbuffer compiles schema-specialized readers and writers. Binary reads use generated decode clauses, while to_iolist/1 and to_binary/1 use a generated reverse builder with precomputed field ordering, offsets, sizes, and alignments. Reads from general iodata retain the interpreted reader fallback and the same public API.

Interoperability tests

The bidirectional integration tests require flatc, the FlatBuffers C++ headers, and a C++17 compiler. Run them separately from the unit suite:

mix test --only flatc

They verify that Flatbuffer decodes a binary produced by flatc and that flatc-generated C++ accessors read every field in a binary produced by Flatbuffer. CI runs them on the newest supported Elixir/OTP combination.

Comparing Flatbuffer to flatc

features both in Flatbuffer and flatc

  • tables
  • scalars
  • strings
  • vtables
  • shared strings (shared field attribute)
  • shared vtables
  • structs
  • alignment
  • unions
  • enums
  • defaults
  • file identifier + validation
  • random access
  • validate file identifiers
  • includes

features only in flatc

  • additional attributes other than shared

About

Elixir/Erlang flatbuffers implementation

Resources

Stars

8 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages