Hi,
I'm currently evaluating FlatBuffers for use in a soft-real-time robotics project. In the past we have been using protobufs, but we are hitting limits with respect to determinism of the Google provided libraries.
Project
We are working on a set of modular actuators/sensors that can be combined in various ways, such as a hexapod (
https://www.youtube.com/watch?v=AMVO6rI5mL4) or a snake robot (https://www.youtube.com/watch?v=wMsDg_1TQxA)
. We would like to be able to support simultaneous communications with hundreds of modules at rates up to 1KHz. Although protobuf works fine for most use cases, there are some corner cases that need to be very deterministic and won't allow use of dynamic memory in Java.
Constraints
- C++: must be compatible with C++98 and run on an ARM7 embedded device
- Java: limited to JDK6, <150MB Heap, no optimization flags, needs to support all major operating systems
- Determinism: no dynamic memory, no garbage collection
- Languages: at least C++ and Java, but Python and C# would be good
- Bandwidth: almost unconstrained. We will likely hit CPU limits far before we hit any bandwidth limit.
Options
So far I've been looking at a few potential solutions
1) 3rd party Java protobuf library with object pooling
Protostuff won't work due to the JDK6 requirement. The other libraries I've found seem outdated and don't provide a reflective API. I'm open for suggestions.
2) JNI to the C++ protobuf API
Providing compiled binaries for all supported systems, and auto-generating JNI wrappers is simply too much work.
3) CapnProto
Very nice concept, but we need to be able to check whether a field has been set.
4) FlatBuffers
The protocol is almost exactly what we would like to see, but the current implementation unfortunately doesn't quite fit.
Overall you guys have done a nice job with this library. I've tried to compile a list with the stuff that I've thought about in the past couple of days. I'd like to get your opinions on whether this is something that may be able to be integrated in the library. Note that so far I've only looked at the Java bindings.
Questions
A) What is the second size uint16_t after voffset_t in sub-tables used for? I understand the use case for deserializing from streams, but that would only require a size in the root object. Are those sizes used anywhere? Is it possible to modify the protocol definition to allow not setting this size for nested objects (the root-size should be enough for streaming)?
B) Why are bytes in the ByteBuffer considered immutable once written (during the building phase)? The protocol can't be streamed, so I don't see the reasoning behind this decision.
Barriers to our Adoption
a) FlatBufferBuilder uses dynamic memory
Currently each object creates a temporary byte[] to store the vtable. This creates so much garbage that the library is unusable for us. There would need to be a way to either recycle them, or the buffer would need to be built without temporary objects (preferred).
b) Writing data
As mentioned above, the current implementation uses a temporary array which generates lots of garbage. Additionally, it feels a bit rigid for such a flexible wire format. I'd like to see a more dynamic way to generate the message, similar to protobuf. I've added a text file showing some ideas (writing.txt)
c) Reading data
The current way of reading data using recycled views is good, but it's not ideal for multithreaded subscribers. In order to not lose determinism, there has to be either a global recycle bin (potential memory leak) or each reader has to keep separate object instances. Unfortunately value types are still long ways off in Java :( I'd propose an additional approach that is closer to C-style pointer manipulation, using addresses and static methods. Addresses could be stored as ints on the stack. I've attached a small sample of how I imagine that this could look like (reading.txt). It should also result in better performance due to fewer indirections.
Minor Issues
d) Don't omit default values
We have different types of modules that respond with the same message, but only populate the subset of fields that they need. Because of this, we need to know whether a field has actually been set, e.g., not sending default values makes it ambiguous whether a sensor doesn't exist or whether it's just reporting the default value. Another concern is that some modules may be running old firmware that was compiled with varying default values. What makes this even worse is that every field has a default value, even if none has been defined in the IDL file. Either there should be no default-default values, or there needs to be a way to opt-out of this.
e) Add hasField() accessors
It would be very nice to be able to check whether a field is populated. This makes most sense in combination with (d).
Suggestions
f) Add setField() into the builder
Is there a good reason why fields written to the write-buffer are considered immutable? A set() could check whether a field is already set, and then either call addField() or overwrite it.
g) Java bindings should conform to the Java guidelines
h) Bool fields should return a boolean, not a byte
"addBoolField((byte)1)" should become "addBoolField(true)"
"boolean x = getBoolField() !=0" should become "boolean x = getBoolField()"
Overall I'm fascinated by the concept and I'm glad that you've open-sourced this. I really appreciate the work you've been putting into it. If you think that these barriers/issues could be eliminated, I'd be happy to switch and offer some help and ideas with the Java implementation.
Thanks,
- Florian
Florian Enner, Principial Systems/Software Engineer
Robotics Institute: Carnegie Mellon University