Add [Dart] support (#4676)

* Add [Dart] support

* fix enum vectors

* Allow for opt out of string interning

* fix comment style, make interning opt in

* remove Offset<T>, prefer int

* avoid creating unnecessary vtable objects

* start work on tests - do not generate builder if struct has 0 fields - add int64

* support reading structs properly

* correctly handle reading vectors of structs, dartfmt

* support structs, fix unnecessary prepares

* fix bool customizations

* undo unintentional removal of file

* docs updates, complete tutorial, bug fix for codegen

* more documentation

* Update docs, add to doxygen file

* update package structure, add samples script/code

* rearrange sample

* Tests

* Add readme for pub

* cleanup package for pub

* update docs for renamed file

* remove custom matcher, use `closeTo` instead

* remove unintentional file

* remove unintended file checkin

* use auto, move method, cleanup

* refactor to ObjectBuilders, add Builders

* Update tests, examples

* Add files missing from previous commit

* documentation and example updates

* Update LICENSE, make dartanalyzer happy, fix minor bugs, get rid of duplicate files, publish script

* fix sample for slightly different schema

* Update pubspec.yaml
This commit is contained in:
Dan Field
2018-05-18 14:06:15 -04:00
committed by Wouter van Oortmerssen
parent c43a0beff0
commit 88912640d0
27 changed files with 5547 additions and 20 deletions

View File

@@ -30,6 +30,7 @@ Please select your desired language for our quest:
<input type="radio" name="language" value="typescript">TypeScript</input>
<input type="radio" name="language" value="php">PHP</input>
<input type="radio" name="language" value="c">C</input>
<input type="radio" name="language" value="dart">Dart</input>
</form>
\endhtmlonly
@@ -132,6 +133,9 @@ For your chosen language, please cross-reference with:
<div class="language-c">
[monster.c](https://github.com/dvidelabs/flatcc/blob/master/samples/monster/monster.c)
</div>
<div class="language-dart">
[example.dart](https://github.com/google/flatbuffers/blob/master/dart/example/example.dart)
</div>
## Writing the Monsters' FlatBuffer Schema
@@ -312,6 +316,12 @@ Please be aware of the difference between `flatc` and `flatcc` tools.
flatcc/samples/monster/build.sh
~~~
</div>
<div class="language-dart">
~~~{.sh}
cd flatbuffers/sample
./../flatc --dart samples/monster.fbs
~~~
</div>
For a more complete guide to using the `flatc` compiler, please read the
[Using the schema compiler](@ref flatbuffers_guide_using_schema_compiler)
@@ -421,6 +431,14 @@ The first step is to import/include the library, generated files, etc.
#define c_vec_len(V) (sizeof(V)/sizeof((V)[0]))
~~~
</div>
<div class="language-dart">
~~~{.dart}
import 'package:flat_buffers/flat_buffers.dart' as fb;
// Generated by `flatc`.
import 'monster_my_game.sample_generated.dart' as myGame;
~~~
</div>
Now we are ready to start building some buffers. In order to start, we need
to create an instance of the `FlatBufferBuilder`, which will contain the buffer
@@ -491,6 +509,15 @@ which will grow automatically if needed:
flatcc_builder_init(B);
~~~
</div>
<div class="language-dart">
~~~{.dart}
// Create the fb.Builder object that will be used by our generated builders
// Note that if you are only planning to immediately get the byte array this builder would create,
// you can use the convenience method `toBytes()` on the generated builders.
// For example, you could do something like `new myGame.MonsterBuilder(...).toBytes()`
var builder = new fb.Builder(initialSize: 1024);
~~~
</div>
After creating the `builder`, we can start serializing our data. Before we make
our `orc` Monster, lets create some `Weapon`s: a `Sword` and an `Axe`.
@@ -633,6 +660,51 @@ our `orc` Monster, lets create some `Weapon`s: a `Sword` and an `Axe`.
ns(Weapon_ref_t) axe = ns(Weapon_create(B, weapon_two_name, weapon_two_damage));
~~~
</div>
<div class="language-dart">
~~~{.dart}
// The generated Builder classes work much like in other languages,
final int weaponOneName = builder.writeString("Sword");
final int weaponOneDamage = 3;
final int weaponTwoName = builder.writeString("Axe");
final int weaponTwoDamage = 5;
final swordBuilder = new myGame.WeaponBuilder(builder)
..begin()
..addNameOffset(weaponOneName)
..addDamage(weaponOneDamage);
final int sword = swordBuilder.finish();
final axeBuilder = new myGame.WeaponBuilder(builder)
..begin()
..addNameOffset(weaponTwoName)
..addDamage(weaponTwoDamage);
final int axe = axeBuilder.finish();
// The genearted ObjectBuilder classes offer an easier to use alternative
// at the cost of requiring some additional reference allocations. If memory
// usage is critical, or if you'll be working with especially large messages
// or tables, you should prefer using the generated Builder classes.
// The following code would produce an identical buffer as above.
final String weaponOneName = "Sword";
final int weaponOneDamage = 3;
final String weaponTwoName = "Axe";
final int weaponTwoDamage = 5;
final myGame.WeaponBuilder sword = new myGame.WeaponObjectBuilder(
name: weaponOneName,
damage: weaponOneDamage,
);
final myGame.WeaponBuilder axe = new myGame.WeaponObjectBuilder(
name: weaponTwoName,
damage: weaponTwoDamage,
);
~~~
</div>
Now let's create our monster, the `orc`. For this `orc`, lets make him
`red` with rage, positioned at `(1.0, 2.0, 3.0)`, and give him
@@ -760,6 +832,26 @@ traversal. This is generally easy to do on any tree structures.
inventory = flatbuffers_uint8_vec_create(B, treasure, c_vec_len(treasure));
~~~
</div>
<div class="language-dart">
~~~{.dart}
// Serialize a name for our monster, called "Orc".
final int name = builder.writeString('Orc');
// Create a list representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
final List<int> treasure = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
final inventory = builder.writeListUint8(treasure);
// The following code should be used instead if you intend to use the
// ObjectBuilder classes:
// Serialize a name for our monster, called "Orc".
final String name = 'Orc';
// Create a list representing the inventory of the Orc. Each number
// could correspond to an item that can be claimed after he is slain.
final List<int> treasure = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
~~~
</div>
We serialized two built-in data types (`string` and `vector`) and captured
their return values. These values are offsets into the serialized data,
@@ -863,6 +955,15 @@ offsets.
ns(Weapon_vec_ref_t) weapons = ns(Weapon_vec_end(B));
~~~
</div>
<div class="language-dart">
~~~{.dart}
// If using the Builder classes, serialize the `[sword,axe]`
final weapons = builder.writeList([sword, axe]);
// If using the ObjectBuilders, just create an array from the two `Weapon`s
final List<myGame.WeaponBuilder> weaps = [sword, axe];
~~~
</div>
<div class="language-cpp">
<br>
@@ -943,6 +1044,25 @@ for the `path` field above:
// TBD
~~~
</div>
<div class="language-dart">
~~~{.dart}
// Using the Builder classes, you can write a list of structs like so:
// Note that the intended order should be reversed if order is important.
final vec3Builder = new myGame.Vec3Builder(builder);
vec3Builder.finish(4.0, 5.0, 6.0);
vec3Builder.finish(1.0, 2.0, 3.0);
final int path = builder.endStructVector(2); // the lenght of the vector
// Otherwise, using the ObjectBuilder classes:
// The dart implementation provides a simple interface for writing vectors
// of structs, in `writeListOfStructs`. This method takes
// `List<ObjectBuilder>` and is used by the generated builder classes.
final List<myGame.Vec3ObjectBuilder> path = [
new myGame.Vec3ObjectBuilder(x: 1.0, y: 2.0, z: 3.0),
new myGame.Vec3ObjectBuilder(x: 4.0, y: 5.0, z: 6.0)
];
~~~
</div>
We have now serialized the non-scalar components of the orc, so we
can serialize the monster itself:
@@ -1095,6 +1215,58 @@ can serialize the monster itself:
weapons, equipped, path));
~~~
</div>
<div class="language-dart">
~~~{.dart}
// Using the Builder API:
// Set his hit points to 300 and his mana to 150.
final int hp = 300;
final int mana = 150;
final monster = new myGame.MonsterBuilder(builder)
..begin()
..addNameOffset(name)
..addInventoryOffset(inventory)
..addWeaponsOffset(weapons)
..addEquippedType(myGame.EquipmentTypeId.Weapon)
..addEquippedOffset(axe)
..addHp(hp)
..addMana(mana)
..addPos(vec3Builder.finish(1.0, 2.0, 3.0))
..addPathOffset(path)
..addColor(myGame.Color.Red);
final int orc = monster.finish();
// -Or- using the ObjectBuilder API:
// Set his hit points to 300 and his mana to 150.
final int hp = 300;
final int mana = 150;
// Note that these parameters are optional - it is not necessary to set
// all of them.
// Also note that it is not necessary to `finish` the builder helpers above
// - the generated code will automatically reuse offsets if the same object
// is used in more than one place (e.g. the axe appearing in `weapons` and
// `equipped`).
final myGame.MonsterBuilder orcBuilder = new myGame.MonsterBuilder(
name: name,
inventory: treasure,
weapons: weaps,
equippedType: myGame.EquipmentTypeId.Weapon,
equipped: axe,
path: path,
hp: hp,
mana: mana,
pos: new myGame.Vec3Builder(x: 1.0, y: 2.0, z: 3.0),
color: myGame.Color.Red,
path: [
new myGame.Vec3ObjectBuilder(x: 1.0, y: 2.0, z: 3.0),
new myGame.Vec3ObjectBuilder(x: 4.0, y: 5.0, z: 6.0)
]);
final int orc = orcBuilder.finish(builder);
~~~
</div>
Note how we create `Vec3` struct in-line in the table. Unlike tables, structs
are simple combinations of scalars that are always stored inline, just like
@@ -1226,6 +1398,17 @@ Here is a repetition these lines, to help highlight them more clearly:
ns(Monster_equipped_Weapon_add(B, axe));
~~~
</div>
<div class="language-dart">
~~~{.dart}
// using the builder API:
..addEquippedType(myGame.EquipmentTypeId.Weapon)
..addEquippedOffset(axe)
// in the ObjectBuilder API:
equippedTypeId: myGame.EquipmentTypeId.Weapon, // Union type
equipped: axe, // Union data
~~~
</div>
After you have created your buffer, you will have the offset to the root of the
data in the `orc` variable, so you can finish the buffer by calling the
@@ -1291,6 +1474,12 @@ appropriate `finish` method.
// Because we used `Monster_create_as_root`, we do not need a `finish` call in C`.
~~~
</div>
<div class="language-dart">
~~~{.dart}
// Call `finish()` to instruct the builder that this monster is complete.
// See the next code section, as in Dart `finish` will also return the byte array.
~~~
</div>
The buffer is now ready to be stored somewhere, sent over the network, be
compressed, or whatever you'd like to do with it. You can access the buffer
@@ -1383,6 +1572,11 @@ like so:
flatcc_builder_clear(B);
~~~
</div>
<div class="language-dart">
~~~{.dart}
final Uint8List buf = builder.finish(orc);
~~~
</div>
Now you can write the bytes to a file, send them over the network..
**Make sure your file mode (or tranfer protocol) is set to BINARY, not text.**
@@ -1490,6 +1684,12 @@ before:
#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Sample, x) // Specified in the schema.
~~~
</div>
<div class="language-dart">
~~~{.dart}
import 'package:flat_buffers/flat_buffers.dart' as fb;
import './monster_my_game.sample_generated.dart' as myGame;
~~~
</div>
Then, assuming you have a buffer of bytes received from disk,
network, etc., you can create start accessing the buffer like so:
@@ -1591,6 +1791,13 @@ won't work**
// Note: root object pointers are NOT the same as the `buffer` pointer.
~~~
</div>
<div class="language-dart">
~~~{.dart}
List<int> data = ... // the data, e.g. from file or network
// A generated factory constructor that will read the data.
myGame.Monster monster = new myGame.Monster(data);
~~~
</div>
If you look in the generated files from the schema compiler, you will see it generated
accessors for all non-`deprecated` fields. For example:
@@ -1611,7 +1818,7 @@ accessors for all non-`deprecated` fields. For example:
</div>
<div class="language-csharp">
~~~{.cs}
// For C#, unlike other languages support by FlatBuffers, most values (except for
// For C#, unlike most other languages support by FlatBuffers, most values (except for
// vectors and unions) are available as propreties instead of asccessor methods.
var hp = monster.Hp
var mana = monster.Mana
@@ -1660,6 +1867,15 @@ accessors for all non-`deprecated` fields. For example:
flatbuffers_string_t name = ns(Monster_name(monster));
~~~
</div>
<div class="language-dart">
~~~{.dart}
// For Dart, unlike other languages support by FlatBuffers, most values
// are available as propreties instead of asccessor methods.
var hp = monster.hp;
var mana = monster.mana;
var name = monster.name;
~~~
</div>
These should hold `300`, `150`, and `"Orc"` respectively.
@@ -1745,6 +1961,14 @@ To access sub-objects, in the case of our `pos`, which is a `Vec3`:
float z = ns(Vec3_z(pos));
~~~
</div>
<div class="language-dart">
~~~{.dart}
myGame.Vec3 pos = monster.pos;
double x = pos.x;
double y = pos.y;
double z = pos.z;
~~~
</div>
`x`, `y`, and `z` will contain `1.0`, `2.0`, and `3.0`, respectively.
@@ -1811,6 +2035,12 @@ FlatBuffers `vector`.
size_t inv_len = flatbuffers_uint8_vec_len(inv);
~~~
</div>
<div class="language-dart">
~~~{.dart}
int invLength = monster.inventory.length;
var thirdItem = monster.inventory[2];
~~~
</div>
For `vector`s of `table`s, you can access the elements like any other vector,
except your need to handle the result as a FlatBuffer `table`:
@@ -1885,6 +2115,13 @@ except your need to handle the result as a FlatBuffer `table`:
uint16_t second_weapon_damage = ns(Weapon_damage(ns(Weapon_vec_at(weapons, 1))));
~~~
</div>
<div class="language-dart">
~~~{.dart}
int weaponsLength = monster.weapons.length;
var secondWeaponName = monster.weapons[1].name;
var secondWeaponDamage = monster.Weapons[1].damage;
~~~
</div>
Last, we can access our `Equipped` FlatBuffer `union`. Just like when we created
the `union`, we need to get both parts of the `union`: the type and the data.
@@ -2008,6 +2245,18 @@ We can access the type to dynamically cast the data as needed (since the
}
~~~
</div>
<div class="language-dart">
~~~{.dart}
var unionType = monster.equippedType.value;
if (unionType == myGame.EquipmentTypeId.Weapon.value) {
myGame.Weapon weapon = mon.equipped as myGame.Weapon;
var weaponName = weapon.name; // "Axe"
var weaponDamage = weapon.damage; // 5
}
~~~
</div>
## Mutating FlatBuffers
@@ -2083,6 +2332,11 @@ mutators like so:
(except in-place vector sorting is possible).>
~~~
</div>
<div class="language-dart">
~~~{.dart}
<API for mutating FlatBuffers not yet available in Dart.>
~~~
</div>
We use the somewhat verbose term `mutate` instead of `set` to indicate that this
is a special use case, not to be confused with the default way of constructing
@@ -2192,5 +2446,8 @@ For your chosen language, see:
<div class="language-c">
[Use in C](@ref flatbuffers_guide_use_c)
</div>
<div class="language-dart">
[Use in Dart](@ref flatbuffers_guide_use_dart)
</div>
<br>