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

@@ -35,6 +35,8 @@ For any schema input files, one or more generators can be specified:
- `--grpc`: Generate RPC stub code for GRPC.
- `--dart`: Generate Dart code.
For any data input files:
- `--binary`, `-b` : If data is contained in this file, generate a

108
docs/source/DartUsage.md Normal file
View File

@@ -0,0 +1,108 @@
Use in Dart {#flatbuffers_guide_use_dart}
===========
## Before you get started
Before diving into the FlatBuffers usage in Dart, it should be noted that
the [Tutorial](@ref flatbuffers_guide_tutorial) page has a complete guide
to general FlatBuffers usage in all of the supported languages (including Dart).
This page is designed to cover the nuances of FlatBuffers usage, specific to
Dart.
You should also have read the [Building](@ref flatbuffers_guide_building)
documentation to build `flatc` and should be familiar with
[Using the schema compiler](@ref flatbuffers_guide_using_schema_compiler) and
[Writing a schema](@ref flatbuffers_guide_writing_schema).
## FlatBuffers Dart library code location
The code for the FlatBuffers Go library can be found at
`flatbuffers/dart`. You can browse the library code on the [FlatBuffers
GitHub page](https://github.com/google/flatbuffers/tree/master/dart).
## Testing the FlatBuffers Dart library
The code to test the Dart library can be found at `flatbuffers/tests`.
The test code itself is located in [dart_test.dart](https://github.com/google/
flatbuffers/blob/master/tests/dart_test.dart).
To run the tests, use the [DartTest.sh](https://github.com/google/flatbuffers/
blob/master/tests/DartTest.sh) shell script.
*Note: The shell script requires the [Dart SDK](https://www.dartlang.org/tools/sdk)
to be installed.*
## Using the FlatBuffers Dart library
*Note: See [Tutorial](@ref flatbuffers_guide_tutorial) for a more in-depth
example of how to use FlatBuffers in Dart.*
FlatBuffers supports reading and writing binary FlatBuffers in Dart.
To use FlatBuffers in your own code, first generate Dart classes from your
schema with the `--dart` option to `flatc`. Then you can include both FlatBuffers
and the generated code to read or write a FlatBuffer.
For example, here is how you would read a FlatBuffer binary file in Dart: First,
include the library and generated code. Then read a FlatBuffer binary file into
a `List<int>`, which you pass to the factory constructor for `Monster`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.dart}
import 'dart:io' as io;
import 'package:flat_buffers/flat_buffers.dart' as fb;
import './monster_my_game.sample_generated.dart' as myGame;
List<int> data = await new io.File('monster.dat').readAsBytes();
var monster = new myGame.Monster(data);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now you can access values like this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.dart}
var hp = monster.hp;
var pos = monster.pos;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Differences from the Dart SDK Front End flat_buffers
The work in this repository is signfiicantly based on the implementation used
internally by the Dart SDK in the front end/analyzer package. Several
significant changes have been made.
1. Support for packed boolean lists has been removed. This is not standard
in other implementations and is not compatible with them. Do note that,
like in the JavaScript implementation, __null values in boolean lists
will be treated as false__. It is also still entirely possible to pack data
in a single scalar field, but that would have to be done on the application
side.
2. The SDK implementation supports enums with regular Dart enums, which
works if enums are always indexed at 1; however, FlatBuffers does not
require that. This implementation uses specialized enum-like classes to
ensure proper mapping from FlatBuffers to Dart and other platforms.
3. The SDK implementation does not appear to support FlatBuffer structs or
vectors of structs - it treated everything as a built-in scalar or a table.
This implementation treats structs in a way that is compatible with other
non-Dart implementations, and properly handles vectors of structs. Many of
the methods prefixed with 'low' have been prepurposed to support this.
4. The SDK implementation treats int64 and uint64 as float64s. This
implementation does not. This may cause problems with JavaScript
compatibility - however, it should be possible to use the JavaScript
implementation, or to do a customized implementation that treats all 64 bit
numbers as floats. Supporting the Dart VM and Flutter was a more important
goal of this implementation. Support for 16 bit integers was also added.
5. The code generation in this offers an "ObjectBuilder", which generates code
very similar to the SDK classes that consume FlatBuffers, as well as Builder
classes, which produces code which more closely resembles the builders in
other languages. The ObjectBuilder classes are easier to use, at the cost of
additional references allocated.
## Text Parsing
There currently is no support for parsing text (Schema's and JSON) directly
from Dart, though you could use the C++ parser through Dart Native Extensions.
Please see the C++ documentation for more on text parsing (note that this is
not currently an option in Flutter - follow [this issue](https://github.com/flutter/flutter/issues/7053)
for the latest).
<br>

View File

@@ -18,23 +18,24 @@ In general:
NOTE: this table is a start, it needs to be extended.
Feature | C++ | Java | C# | Go | Python | JS | TS | C | PHP | Ruby
------------------------------ | ------ | ------ | ------ | ------ | ------ | --------- | --------- | ------ | --- | ----
Codegen for all basic features | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | WiP | WiP
JSON parsing | Yes | No | No | No | No | No | No | Yes | No | No
Simple mutation | Yes | Yes | Yes | Yes | No | No | No | No | No | No
Reflection | Yes | No | No | No | No | No | No | Basic | No | No
Buffer verifier | Yes | No | No | No | No | No | No | Yes | No | No
Testing: basic | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | ? | ?
Testing: fuzz | Yes | No | No | Yes | Yes | No | No | No | ? | ?
Performance: | Superb | Great | Great | Great | Ok | ? | ? | Superb | ? | ?
Platform: Windows | VS2010 | Yes | Yes | ? | ? | ? | Yes | VS2010 | ? | ?
Platform: Linux | GCC282 | Yes | ? | Yes | Yes | ? | Yes | Yes | ? | ?
Platform: OS X | Xcode4 | ? | ? | ? | Yes | ? | Yes | Yes | ? | ?
Platform: Android | NDK10d | Yes | ? | ? | ? | ? | ? | ? | ? | ?
Platform: iOS | ? | ? | ? | ? | ? | ? | ? | ? | ? | ?
Engine: Unity | ? | ? | Yes | ? | ? | ? | ? | ? | ? | ?
Primary authors (github) | gwvo | gwvo | ev*/js*| rw | rw | evanw/ev* | kr | mik* | ch* | rw
Feature | C++ | Java | C# | Go | Python | JS | TS | C | PHP | Ruby | Dart
------------------------------ | ------ | ------ | ------ | ------ | ------ | --------- | --------- | ------ | --- | ---- | ----
Codegen for all basic features | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | WiP | WiP | Yes
JSON parsing | Yes | No | No | No | No | No | No | Yes | No | No | No
Simple mutation | Yes | Yes | Yes | Yes | No | No | No | No | No | No | No
Reflection | Yes | No | No | No | No | No | No | Basic | No | No | No
Buffer verifier | Yes | No | No | No | No | No | No | Yes | No | No | No
Testing: basic | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | ? | ? | Yes
Testing: fuzz | Yes | No | No | Yes | Yes | No | No | No | ? | ? | No
Performance: | Superb | Great | Great | Great | Ok | ? | ? | Superb | ? | ? | ?
Platform: Windows | VS2010 | Yes | Yes | ? | ? | ? | Yes | VS2010 | ? | ? | Yes
Platform: Linux | GCC282 | Yes | ? | Yes | Yes | ? | Yes | Yes | ? | ? | Yes
Platform: OS X | Xcode4 | ? | ? | ? | Yes | ? | Yes | Yes | ? | ? | Yes
Platform: Android | NDK10d | Yes | ? | ? | ? | ? | ? | ? | ? | ? | Flutter
Platform: iOS | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | Flutter
Engine: Unity | ? | ? | Yes | ? | ? | ? | ? | ? | ? | ? | ?
Primary authors (github) | gwvo | gwvo | ev*/js*| rw | rw | evanw/ev* | kr | mik* | ch* | rw | dnfield
* ev = evolutional
* js = jonsimantov

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>

View File

@@ -751,6 +751,7 @@ INPUT = "FlatBuffers.md" \
"Schemas.md" \
"CppUsage.md" \
"CUsage.md" \
"DartUsage.md" \
"GoUsage.md" \
"JavaCsharpUsage.md" \
"JavaScriptUsage.md" \

View File

@@ -39,6 +39,8 @@
title="Use in PHP"/>
<tab type="user" url="@ref flatbuffers_guide_use_python"
title="Use in Python"/>
<tab type="user" url="@ref flatbuffers_guide_use_dart"
title="Use in Dart"/>
<tab type="user" url="@ref flexbuffers"
title="Schema-less version"/>
<tab type="usergroup" url="" title="gRPC">