Create a Haskell module named after the namespace in the schema.
module Data.Game where
Use mkFlatBuffers to generate constructors and accessors for the data types in your schema.
{-# LANGUAGE TemplateHaskell #-}
module Data.Game where
import FlatBuffers
$(mkFlatBuffers "schemas/game.fbs" defaultOptions)
The following declarations will be generated for you.
data Monster
-- Constructor
monster :: Maybe Text -> Maybe Int32 -> WriteVector Text -> WriteTable Monster
-- Accessors
monsterName :: Table Monster -> Either ReadError (Maybe Text)
monsterHp :: Table Monster -> Either ReadError Int32
monsterLocations :: Table Monster -> Either ReadError (Vector Text)
We can now construct a flatbuffer using encode and read it using decode:
{-# LANGUAGE OverloadedStrings #-}
import FlatBuffers
import qualified FlatBuffers.Vector as Vector
-- Writing
let byteString = encode $
monster
(Just "Poring")
(Just 50)
(Vector.fromList 2 ["Prontera Field", "Payon Forest"])
-- Reading
do
someMonster <- decode byteString
name <- monsterName someMonster
hp <- monsterHp someMonster
locations <- monsterLocations someMonster >>= Vector.toList
Right ("Monster: " <> show name <> " (" <> show hp <> " HP) can be found in " <> show locations)
For more info on code generation and examples, see codegen.
Enums
enum Color: short { Red, Green, Blue }
Given the enum declarationa above, the following code will be generated:
data Color
= ColorRed
| ColorGreen
| ColorBlue
deriving (Eq, Show, Read, Ord, Bounded)
toColor :: Int16 -> Maybe Color
fromColor :: Color -> Int16
Usage:
table Monster {
color: Color;
}
data Monster
monster :: Maybe Int16 -> WriteTable Monster
monsterColor :: Table Monster -> Either ReadError Int16
-- Writing
let byteString = encode $
monster (Just (fromColor ColorBlue))
-- Reading
do
someMonster <- decode byteString
short <- monsterColor someMonster
case toColor short of
Just ColorRed -> Right "This monster is red"
Just ColorGreen -> Right "This monster is green"
Just ColorBlue -> Right "This monster is blue"
Nothing -> Left ("Unknown color: " <> show short) -- Forwards compatibility
Structs
struct Coord {
x: long;
y: long;
}
Given the struct declaration above, the following code will be generated:
data Monster
monster :: WriteStruct Coord -> WriteTable Monster
monsterPosition :: Table Monster -> Either ReadError (Struct Coord)
-- Writing
let byteString = encode $
monster (coord 123 456)
-- Reading
do
someMonster <- decode byteString
pos <- monsterPosition someMonster
x <- coordX pos
y <- coordY pos
Right ("Monster is located at " <> show x <> ", " <> show y)
data Character
character :: WriteUnion Weapon -> WriteTable Character
characterWeapon :: Table Character -> Either ReadError (Union Weapon)
-- Writing
let byteString = encode $
character
(weaponSword (sword (Just 1000)))
-- Reading
do
someCharacter <- decode byteString
weapon <- characterWeapon someCharacter
case weapon of
Union (WeaponSword sword) -> do
power <- swordPower sword
Right ("Weilding a sword with " <> show power <> " Power.")
Union (WeaponAxe axe) -> do
power <- axePower axe
Right ("Weilding an axe with " <> show power <> " Power.")
UnionNone -> Right "Character has no weapon"
UnionUnknown byte -> Left "Unknown weapon" -- Forwards compatibility
Note that, like in the official FlatBuffers implementation, unions are always optional.
Adding the required attribute to a union field has no effect.
To create a character with no weapon, use none :: WriteUnion a
Typically, a FlatBuffer binary buffer is not self-describing, i.e. it needs you to know its schema to parse it correctly. But if you want to use a FlatBuffer as a file format, it would be convenient to be able to have a "magic number" in there, like most file formats have, to be able to do a sanity check to see if you're reading the kind of file you're expecting.
Now, you can always prefix a FlatBuffer with your own file header, but FlatBuffers has a built-in way to add an identifier to a FlatBuffer that takes up minimal space, and keeps the buffer compatible with buffers that don't have such an identifier.
There are lots of examples in the test/Examples folder and the THSpec module.
In particular, test/Examples/schema.fbs and test/Examples/vector_of_unions.fbs contain a variety of data structures and Examples.HandWritten demonstrates what the code generated by mkFlatBuffers would look like.
TODO
Features
gRPC support
Size-prefixed buffers (needed for streaming multiple messages)