Merge pull request #4 from google/master
[platform/upstream/flatbuffers.git] / docs / source / Internals.md
1 FlatBuffer Internals    {#flatbuffers_internals}
2 ====================
3
4 This section is entirely optional for the use of FlatBuffers. In normal
5 usage, you should never need the information contained herein. If you're
6 interested however, it should give you more of an appreciation of why
7 FlatBuffers is both efficient and convenient.
8
9 ### Format components
10
11 A FlatBuffer is a binary file and in-memory format consisting mostly of
12 scalars of various sizes, all aligned to their own size. Each scalar is
13 also always represented in little-endian format, as this corresponds to
14 all commonly used CPUs today. FlatBuffers will also work on big-endian
15 machines, but will be slightly slower because of additional
16 byte-swap intrinsics.
17
18 On purpose, the format leaves a lot of details about where exactly
19 things live in memory undefined, e.g. fields in a table can have any
20 order, and objects to some extent can be stored in many orders. This is
21 because the format doesn't need this information to be efficient, and it
22 leaves room for optimization and extension (for example, fields can be
23 packed in a way that is most compact). Instead, the format is defined in
24 terms of offsets and adjacency only. This may mean two different
25 implementations may produce different binaries given the same input
26 values, and this is perfectly valid.
27
28 ### Format identification
29
30 The format also doesn't contain information for format identification
31 and versioning, which is also by design. FlatBuffers is a statically typed
32 system, meaning the user of a buffer needs to know what kind of buffer
33 it is. FlatBuffers can of course be wrapped inside other containers
34 where needed, or you can use its union feature to dynamically identify
35 multiple possible sub-objects stored. Additionally, it can be used
36 together with the schema parser if full reflective capabilities are
37 desired.
38
39 Versioning is something that is intrinsically part of the format (the
40 optionality / extensibility of fields), so the format itself does not
41 need a version number (it's a meta-format, in a sense). We're hoping
42 that this format can accommodate all data needed. If format breaking
43 changes are ever necessary, it would become a new kind of format rather
44 than just a variation.
45
46 ### Offsets
47
48 The most important and generic offset type (see `flatbuffers.h`) is
49 `uoffset_t`, which is currently always a `uint32_t`, and is used to
50 refer to all tables/unions/strings/vectors (these are never stored
51 in-line). 32bit is
52 intentional, since we want to keep the format binary compatible between
53 32 and 64bit systems, and a 64bit offset would bloat the size for almost
54 all uses. A version of this format with 64bit (or 16bit) offsets is easy to set
55 when needed. Unsigned means they can only point in one direction, which
56 typically is forward (towards a higher memory location). Any backwards
57 offsets will be explicitly marked as such.
58
59 The format starts with an `uoffset_t` to the root object in the buffer.
60
61 We have two kinds of objects, structs and tables.
62
63 ### Structs
64
65 These are the simplest, and as mentioned, intended for simple data that
66 benefits from being extra efficient and doesn't need versioning /
67 extensibility. They are always stored inline in their parent (a struct,
68 table, or vector) for maximum compactness. Structs define a consistent
69 memory layout where all components are aligned to their size, and
70 structs aligned to their largest scalar member. This is done independent
71 of the alignment rules of the underlying compiler to guarantee a cross
72 platform compatible layout. This layout is then enforced in the generated
73 code.
74
75 ### Tables
76
77 Unlike structs, these are not stored in inline in their parent, but are
78 referred to by offset.
79
80 They start with an `soffset_t` to a vtable. This is a signed version of
81 `uoffset_t`, since vtables may be stored anywhere relative to the object.
82 This offset is substracted (not added) from the object start to arrive at
83 the vtable start. This offset is followed by all the
84 fields as aligned scalars (or offsets). Unlike structs, not all fields
85 need to be present. There is no set order and layout.
86
87 To be able to access fields regardless of these uncertainties, we go
88 through a vtable of offsets. Vtables are shared between any objects that
89 happen to have the same vtable values.
90
91 The elements of a vtable are all of type `voffset_t`, which is
92 a `uint16_t`. The first element is the size of the vtable in bytes,
93 including the size element. The second one is the size of the object, in bytes
94 (including the vtable offset). This size could be used for streaming, to know
95 how many bytes to read to be able to access all *inline* fields of the object.
96 The remaining elements are the N offsets, where N is the amount of fields
97 declared in the schema when the code that constructed this buffer was
98 compiled (thus, the size of the table is N + 2).
99
100 All accessor functions in the generated code for tables contain the
101 offset into this table as a constant. This offset is checked against the
102 first field (the number of elements), to protect against newer code
103 reading older data. If this offset is out of range, or the vtable entry
104 is 0, that means the field is not present in this object, and the
105 default value is return. Otherwise, the entry is used as offset to the
106 field to be read.
107
108 ### Strings and Vectors
109
110 Strings are simply a vector of bytes, and are always
111 null-terminated. Vectors are stored as contiguous aligned scalar
112 elements prefixed by a 32bit element count (not including any
113 null termination). Neither is stored inline in their parent, but are referred to
114 by offset.
115
116 ### Construction
117
118 The current implementation constructs these buffers backwards (starting
119 at the highest memory address of the buffer), since
120 that significantly reduces the amount of bookkeeping and simplifies the
121 construction API.
122
123 ### Code example
124
125 Here's an example of the code that gets generated for the `samples/monster.fbs`.
126 What follows is the entire file, broken up by comments:
127
128     // automatically generated, do not modify
129
130     #include "flatbuffers/flatbuffers.h"
131
132     namespace MyGame {
133     namespace Sample {
134
135 Nested namespace support.
136
137     enum {
138       Color_Red = 0,
139       Color_Green = 1,
140       Color_Blue = 2,
141     };
142
143     inline const char **EnumNamesColor() {
144       static const char *names[] = { "Red", "Green", "Blue", nullptr };
145       return names;
146     }
147
148     inline const char *EnumNameColor(int e) { return EnumNamesColor()[e]; }
149
150 Enums and convenient reverse lookup.
151
152     enum {
153       Any_NONE = 0,
154       Any_Monster = 1,
155     };
156
157     inline const char **EnumNamesAny() {
158       static const char *names[] = { "NONE", "Monster", nullptr };
159       return names;
160     }
161
162     inline const char *EnumNameAny(int e) { return EnumNamesAny()[e]; }
163
164 Unions share a lot with enums.
165
166     struct Vec3;
167     struct Monster;
168
169 Predeclare all data types since circular references between types are allowed
170 (circular references between object are not, though).
171
172     MANUALLY_ALIGNED_STRUCT(4) Vec3 {
173      private:
174       float x_;
175       float y_;
176       float z_;
177
178      public:
179       Vec3(float x, float y, float z)
180         : x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), z_(flatbuffers::EndianScalar(z)) {}
181
182       float x() const { return flatbuffers::EndianScalar(x_); }
183       float y() const { return flatbuffers::EndianScalar(y_); }
184       float z() const { return flatbuffers::EndianScalar(z_); }
185     };
186     STRUCT_END(Vec3, 12);
187
188 These ugly macros do a couple of things: they turn off any padding the compiler
189 might normally do, since we add padding manually (though none in this example),
190 and they enforce alignment chosen by FlatBuffers. This ensures the layout of
191 this struct will look the same regardless of compiler and platform. Note that
192 the fields are private: this is because these store little endian scalars
193 regardless of platform (since this is part of the serialized data).
194 `EndianScalar` then converts back and forth, which is a no-op on all current
195 mobile and desktop platforms, and a single machine instruction on the few
196 remaining big endian platforms.
197
198     struct Monster : private flatbuffers::Table {
199       const Vec3 *pos() const { return GetStruct<const Vec3 *>(4); }
200       int16_t mana() const { return GetField<int16_t>(6, 150); }
201       int16_t hp() const { return GetField<int16_t>(8, 100); }
202       const flatbuffers::String *name() const { return GetPointer<const flatbuffers::String *>(10); }
203       const flatbuffers::Vector<uint8_t> *inventory() const { return GetPointer<const flatbuffers::Vector<uint8_t> *>(14); }
204       int8_t color() const { return GetField<int8_t>(16, 2); }
205     };
206
207 Tables are a bit more complicated. A table accessor struct is used to point at
208 the serialized data for a table, which always starts with an offset to its
209 vtable. It derives from `Table`, which contains the `GetField` helper functions.
210 GetField takes a vtable offset, and a default value. It will look in the vtable
211 at that offset. If the offset is out of bounds (data from an older version) or
212 the vtable entry is 0, the field is not present and the default is returned.
213 Otherwise, it uses the entry as an offset into the table to locate the field.
214
215     struct MonsterBuilder {
216       flatbuffers::FlatBufferBuilder &fbb_;
217       flatbuffers::uoffset_t start_;
218       void add_pos(const Vec3 *pos) { fbb_.AddStruct(4, pos); }
219       void add_mana(int16_t mana) { fbb_.AddElement<int16_t>(6, mana, 150); }
220       void add_hp(int16_t hp) { fbb_.AddElement<int16_t>(8, hp, 100); }
221       void add_name(flatbuffers::Offset<flatbuffers::String> name) { fbb_.AddOffset(10, name); }
222       void add_inventory(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> inventory) { fbb_.AddOffset(14, inventory); }
223       void add_color(int8_t color) { fbb_.AddElement<int8_t>(16, color, 2); }
224       MonsterBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); }
225       flatbuffers::Offset<Monster> Finish() { return flatbuffers::Offset<Monster>(fbb_.EndTable(start_, 7)); }
226     };
227
228 `MonsterBuilder` is the base helper struct to construct a table using a
229 `FlatBufferBuilder`. You can add the fields in any order, and the `Finish`
230 call will ensure the correct vtable gets generated.
231
232     inline flatbuffers::Offset<Monster> CreateMonster(flatbuffers::FlatBufferBuilder &_fbb,
233                                                       const Vec3 *pos, int16_t mana,
234                                                       int16_t hp,
235                                                       flatbuffers::Offset<flatbuffers::String> name,
236                                                       flatbuffers::Offset<flatbuffers::Vector<uint8_t>> inventory,
237                                                       int8_t color) {
238       MonsterBuilder builder_(_fbb);
239       builder_.add_inventory(inventory);
240       builder_.add_name(name);
241       builder_.add_pos(pos);
242       builder_.add_hp(hp);
243       builder_.add_mana(mana);
244       builder_.add_color(color);
245       return builder_.Finish();
246     }
247
248 `CreateMonster` is a convenience function that calls all functions in
249 `MonsterBuilder` above for you. Note that if you pass values which are
250 defaults as arguments, it will not actually construct that field, so
251 you can probably use this function instead of the builder class in
252 almost all cases.
253
254     inline const Monster *GetMonster(const void *buf) { return flatbuffers::GetRoot<Monster>(buf); }
255
256 This function is only generated for the root table type, to be able to
257 start traversing a FlatBuffer from a raw buffer pointer.
258
259     }; // namespace MyGame
260     }; // namespace Sample
261
262 ### Encoding example.
263
264 Below is a sample encoding for the following JSON corresponding to the above
265 schema:
266
267     { pos: { x: 1, y: 2, z: 3 }, name: "fred", hp: 50 }
268
269 Resulting in this binary buffer:
270
271     // Start of the buffer:
272     uint32_t 20  // Offset to the root table.
273
274     // Start of the vtable. Not shared in this example, but could be:
275     uint16_t 16 // Size of table, starting from here.
276     uint16_t 22 // Size of object inline data.
277     uint16_t 4, 0, 20, 16, 0, 0  // Offsets to fields from start of (root) table, 0 for not present.
278
279     // Start of the root table:
280     int32_t 16     // Offset to vtable used (default negative direction)
281     float 1, 2, 3  // the Vec3 struct, inline.
282     uint32_t 8     // Offset to the name string.
283     int16_t 50     // hp field.
284     int16_t 0      // Padding for alignment.
285
286     // Start of name string:
287     uint32_t 4  // Length of string.
288     int8_t 'f', 'r', 'e', 'd', 0, 0, 0, 0  // Text + 0 termination + padding.
289
290 Note that this not the only possible encoding, since the writer has some
291 flexibility in which of the children of root object to write first (though in
292 this case there's only one string), and what order to write the fields in.
293 Different orders may also cause different alignments to happen.
294
295 <br>