import 'dart:collection'; import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; const int _sizeofUint8 = 1; const int _sizeofUint16 = 2; const int _sizeofUint32 = 4; const int _sizeofUint64 = 8; const int _sizeofInt8 = 1; const int _sizeofInt16 = 2; const int _sizeofInt32 = 4; const int _sizeofInt64 = 8; const int _sizeofFloat32 = 4; const int _sizeofFloat64 = 8; /// Callback used to invoke a struct builder's finish method. /// /// This callback is used by other struct's `finish` methods to write the nested /// struct's fields inline. typedef StructBuilder = void Function(); /// Buffer with data and some context about it. class BufferContext { final ByteData _buffer; ByteData get buffer => _buffer; /// Create from a FlatBuffer represented by a list of bytes (uint8). factory BufferContext.fromBytes(List byteList) => BufferContext(byteList is Uint8List ? byteList.buffer.asByteData(byteList.offsetInBytes) : ByteData.view(Uint8List.fromList(byteList).buffer)); /// Create from a FlatBuffer represented by ByteData. BufferContext(this._buffer); @pragma('vm:prefer-inline') int derefObject(int offset) => offset + _getUint32(offset); @pragma('vm:prefer-inline') Uint8List _asUint8List(int offset, int length) => _buffer.buffer.asUint8List(_buffer.offsetInBytes + offset, length); @pragma('vm:prefer-inline') double _getFloat64(int offset) => _buffer.getFloat64(offset, Endian.little); @pragma('vm:prefer-inline') double _getFloat32(int offset) => _buffer.getFloat32(offset, Endian.little); @pragma('vm:prefer-inline') int _getInt64(int offset) => _buffer.getInt64(offset, Endian.little); @pragma('vm:prefer-inline') int _getInt32(int offset) => _buffer.getInt32(offset, Endian.little); @pragma('vm:prefer-inline') int _getInt16(int offset) => _buffer.getInt16(offset, Endian.little); @pragma('vm:prefer-inline') int _getInt8(int offset) => _buffer.getInt8(offset); @pragma('vm:prefer-inline') int _getUint64(int offset) => _buffer.getUint64(offset, Endian.little); @pragma('vm:prefer-inline') int _getUint32(int offset) => _buffer.getUint32(offset, Endian.little); @pragma('vm:prefer-inline') int _getUint16(int offset) => _buffer.getUint16(offset, Endian.little); @pragma('vm:prefer-inline') int _getUint8(int offset) => _buffer.getUint8(offset); } /// Interface implemented by the "object-api" classes (ending with "T"). abstract class Packable { /// Serialize the object using the given builder, returning the offset. int pack(Builder fbBuilder); } /// Class implemented by typed builders generated by flatc. abstract class ObjectBuilder { int? _firstOffset; /// Can be used to write the data represented by this builder to the [Builder] /// and reuse the offset created in multiple tables. /// /// Note that this method assumes you call it using the same [Builder] instance /// every time. The returned offset is only good for the [Builder] used in the /// first call to this method. int getOrCreateOffset(Builder fbBuilder) { _firstOffset ??= finish(fbBuilder); return _firstOffset!; } /// Writes the data in this helper to the [Builder]. int finish(Builder fbBuilder); /// Convenience method that will create a new [Builder], [finish]es the data, /// and returns the buffer as a [Uint8List] of bytes. Uint8List toBytes(); } /// Class that helps building flat buffers. class Builder { bool _finished = false; final int initialSize; /// The list of existing VTable(s). final List _vTables; final bool deduplicateTables; ByteData _buf; final Allocator _allocator; /// The maximum alignment that has been seen so far. If [_buf] has to be /// reallocated in the future (to insert room at its start for more bytes) the /// reallocation will need to be a multiple of this many bytes. int _maxAlign = 1; /// The number of bytes that have been written to the buffer so far. The /// most recently written byte is this many bytes from the end of [_buf]. int _tail = 0; /// The location of the end of the current table, measured in bytes from the /// end of [_buf]. int _currentTableEndTail = 0; _VTable? _currentVTable; /// Map containing all strings that have been written so far. This allows us /// to avoid duplicating strings. /// /// Allocated only if `internStrings` is set to true on the constructor. Map? _strings; /// Creates a new FlatBuffers Builder. /// /// `initialSize` is the initial array size in bytes. The [Builder] will /// automatically grow the array if/as needed. `internStrings`, if set to /// true, will cause [writeString] to pool strings in the buffer so that /// identical strings will always use the same offset in tables. Builder({ this.initialSize = 1024, bool internStrings = false, Allocator allocator = const DefaultAllocator(), this.deduplicateTables = true, }) : _allocator = allocator, _buf = allocator.allocate(initialSize), _vTables = deduplicateTables ? [] : const [] { if (internStrings) { _strings = {}; } } /// Calculate the finished buffer size (aligned). @pragma('vm:prefer-inline') int size() => _tail + ((-_tail) & (_maxAlign - 1)); /// Add the [field] with the given boolean [value]. The field is not added if /// the [value] is equal to [def]. Booleans are stored as 8-bit fields with /// `0` for `false` and `1` for `true`. void addBool(int field, bool? value, [bool? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofUint8, 1); _trackField(field); _buf.setInt8(_buf.lengthInBytes - _tail, value ? 1 : 0); } } /// Add the [field] with the given 32-bit signed integer [value]. The field is /// not added if the [value] is equal to [def]. void addInt32(int field, int? value, [int? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofInt32, 1); _trackField(field); _setInt32AtTail(_tail, value); } } /// Add the [field] with the given 32-bit signed integer [value]. The field is /// not added if the [value] is equal to [def]. void addInt16(int field, int? value, [int? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofInt16, 1); _trackField(field); _setInt16AtTail(_tail, value); } } /// Add the [field] with the given 8-bit signed integer [value]. The field is /// not added if the [value] is equal to [def]. void addInt8(int field, int? value, [int? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofInt8, 1); _trackField(field); _setInt8AtTail(_tail, value); } } void addStruct(int field, int offset) { assert(_inVTable); _trackField(field); _currentVTable!.addField(field, offset); } /// Add the [field] referencing an object with the given [offset]. void addOffset(int field, int? offset) { assert(_inVTable); if (offset != null) { _prepare(_sizeofUint32, 1); _trackField(field); _setUint32AtTail(_tail, _tail - offset); } } /// Add the [field] with the given 32-bit unsigned integer [value]. The field /// is not added if the [value] is equal to [def]. void addUint32(int field, int? value, [int? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofUint32, 1); _trackField(field); _setUint32AtTail(_tail, value); } } /// Add the [field] with the given 32-bit unsigned integer [value]. The field /// is not added if the [value] is equal to [def]. void addUint16(int field, int? value, [int? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofUint16, 1); _trackField(field); _setUint16AtTail(_tail, value); } } /// Add the [field] with the given 8-bit unsigned integer [value]. The field /// is not added if the [value] is equal to [def]. void addUint8(int field, int? value, [int? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofUint8, 1); _trackField(field); _setUint8AtTail(_tail, value); } } /// Add the [field] with the given 32-bit float [value]. The field /// is not added if the [value] is equal to [def]. void addFloat32(int field, double? value, [double? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofFloat32, 1); _trackField(field); _setFloat32AtTail(_tail, value); } } /// Add the [field] with the given 64-bit double [value]. The field /// is not added if the [value] is equal to [def]. void addFloat64(int field, double? value, [double? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofFloat64, 1); _trackField(field); _setFloat64AtTail(_tail, value); } } /// Add the [field] with the given 64-bit unsigned integer [value]. The field /// is not added if the [value] is equal to [def]. void addUint64(int field, int? value, [double? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofUint64, 1); _trackField(field); _setUint64AtTail(_tail, value); } } /// Add the [field] with the given 64-bit unsigned integer [value]. The field /// is not added if the [value] is equal to [def]. void addInt64(int field, int? value, [double? def]) { assert(_inVTable); if (value != null && value != def) { _prepare(_sizeofInt64, 1); _trackField(field); _setInt64AtTail(_tail, value); } } /// End the current table and return its offset. int endTable() { assert(_inVTable); // Prepare for writing the VTable. _prepare(_sizeofInt32, 1); final tableTail = _tail; // Prepare the size of the current table. final currentVTable = _currentVTable!; currentVTable.tableSize = tableTail - _currentTableEndTail; // Prepare the VTable to use for the current table. int? vTableTail; { currentVTable.computeFieldOffsets(tableTail); // Try to find an existing compatible VTable. if (deduplicateTables) { // Search backward - more likely to have recently used one for (var i = _vTables.length - 1; i >= 0; i--) { final vt2Offset = _vTables[i]; final vt2Start = _buf.lengthInBytes - vt2Offset; final vt2Size = _buf.getUint16(vt2Start, Endian.little); if (currentVTable._vTableSize == vt2Size && currentVTable._offsetsMatch(vt2Start, _buf)) { vTableTail = vt2Offset; break; } } } // Write a new VTable. if (vTableTail == null) { _prepare(_sizeofUint16, _currentVTable!.numOfUint16); vTableTail = _tail; currentVTable.tail = vTableTail; currentVTable.output(_buf, _buf.lengthInBytes - _tail); if (deduplicateTables) _vTables.add(currentVTable.tail); } } // Set the VTable offset. _setInt32AtTail(tableTail, vTableTail - tableTail); // Done with this table. _currentVTable = null; return tableTail; } /// Returns the finished buffer. You must call [finish] before accessing this. @pragma('vm:prefer-inline') Uint8List get buffer { assert(_finished); final finishedSize = size(); return _buf.buffer .asUint8List(_buf.lengthInBytes - finishedSize, finishedSize); } /// Finish off the creation of the buffer. The given [offset] is used as the /// root object offset, and usually references directly or indirectly every /// written object. If [fileIdentifier] is specified (and not `null`), it is /// interpreted as a 4-byte Latin-1 encoded string that should be placed at /// bytes 4-7 of the file. void finish(int offset, [String? fileIdentifier]) { final sizeBeforePadding = size(); final requiredBytes = _sizeofUint32 * (fileIdentifier == null ? 1 : 2); _prepare(max(requiredBytes, _maxAlign), 1); final finishedSize = size(); _setUint32AtTail(finishedSize, finishedSize - offset); if (fileIdentifier != null) { for (var i = 0; i < 4; i++) { _setUint8AtTail( finishedSize - _sizeofUint32 - i, fileIdentifier.codeUnitAt(i)); } } // zero out the added padding for (var i = sizeBeforePadding + 1; i <= finishedSize - requiredBytes; i++) { _setUint8AtTail(i, 0); } _finished = true; } /// Writes a Float64 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putFloat64(double value) { _prepare(_sizeofFloat64, 1); _setFloat32AtTail(_tail, value); } /// Writes a Float32 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putFloat32(double value) { _prepare(_sizeofFloat32, 1); _setFloat32AtTail(_tail, value); } /// Writes a bool to the tail of the buffer after preparing space for it. /// Bools are represented as a Uint8, with the value set to '1' for true, and '0' for false /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putBool(bool value) { _prepare(_sizeofUint8, 1); _buf.setInt8(_buf.lengthInBytes - _tail, value ? 1 : 0); } /// Writes a Int64 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putInt64(int value) { _prepare(_sizeofInt64, 1); _setInt64AtTail(_tail, value); } /// Writes a Uint32 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putInt32(int value) { _prepare(_sizeofInt32, 1); _setInt32AtTail(_tail, value); } /// Writes a Uint16 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putInt16(int value) { _prepare(_sizeofInt16, 1); _setInt16AtTail(_tail, value); } /// Writes a Uint8 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putInt8(int value) { _prepare(_sizeofInt8, 1); _buf.setInt8(_buf.lengthInBytes - _tail, value); } /// Writes a Uint64 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putUint64(int value) { _prepare(_sizeofUint64, 1); _setUint64AtTail(_tail, value); } /// Writes a Uint32 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putUint32(int value) { _prepare(_sizeofUint32, 1); _setUint32AtTail(_tail, value); } /// Writes a Uint16 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putUint16(int value) { _prepare(_sizeofUint16, 1); _setUint16AtTail(_tail, value); } /// Writes a Uint8 to the tail of the buffer after preparing space for it. /// /// Updates the [offset] pointer. This method is intended for use when writing structs to the buffer. void putUint8(int value) { _prepare(_sizeofUint8, 1); _buf.setUint8(_buf.lengthInBytes - _tail, value); } /// Reset the builder and make it ready for filling a new buffer. void reset() { _finished = false; _maxAlign = 1; _tail = 0; _currentVTable = null; if (deduplicateTables) _vTables.clear(); if (_strings != null) { _strings = {}; } } /// Start a new table. Must be finished with [endTable] invocation. void startTable(int numFields) { assert(!_inVTable); // Inline tables are not supported. _currentVTable = _VTable(numFields); _currentTableEndTail = _tail; } /// Finish a Struct vector. Most callers should preferto use [writeListOfStructs]. /// /// Most callers should prefer [writeListOfStructs]. int endStructVector(int count) { putUint32(count); return _tail; } /// Writes a list of Structs to the buffer, returning the offset int writeListOfStructs(List structBuilders) { assert(!_inVTable); for (var i = structBuilders.length - 1; i >= 0; i--) { structBuilders[i].finish(this); } return endStructVector(structBuilders.length); } /// Write the given list of [values]. int writeList(List values) { assert(!_inVTable); _prepare(_sizeofUint32, 1 + values.length); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setUint32AtTail(tail, tail - value); tail -= _sizeofUint32; } return result; } /// Write the given list of 64-bit float [values]. int writeListFloat64(List values) { assert(!_inVTable); _prepare(_sizeofFloat64, values.length, additionalBytes: _sizeofUint32); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setFloat64AtTail(tail, value); tail -= _sizeofFloat64; } return result; } /// Write the given list of 32-bit float [values]. int writeListFloat32(List values) { assert(!_inVTable); _prepare(_sizeofFloat32, 1 + values.length); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setFloat32AtTail(tail, value); tail -= _sizeofFloat32; } return result; } /// Write the given list of signed 64-bit integer [values]. int writeListInt64(List values) { assert(!_inVTable); _prepare(_sizeofInt64, values.length, additionalBytes: _sizeofUint32); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setInt64AtTail(tail, value); tail -= _sizeofInt64; } return result; } /// Write the given list of signed 64-bit integer [values]. int writeListUint64(List values) { assert(!_inVTable); _prepare(_sizeofUint64, values.length, additionalBytes: _sizeofUint32); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setUint64AtTail(tail, value); tail -= _sizeofUint64; } return result; } /// Write the given list of signed 32-bit integer [values]. int writeListInt32(List values) { assert(!_inVTable); _prepare(_sizeofUint32, 1 + values.length); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setInt32AtTail(tail, value); tail -= _sizeofInt32; } return result; } /// Write the given list of unsigned 32-bit integer [values]. int writeListUint32(List values) { assert(!_inVTable); _prepare(_sizeofUint32, 1 + values.length); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setUint32AtTail(tail, value); tail -= _sizeofUint32; } return result; } /// Write the given list of signed 16-bit integer [values]. int writeListInt16(List values) { assert(!_inVTable); _prepare(_sizeofUint32, 1, additionalBytes: 2 * values.length); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setInt16AtTail(tail, value); tail -= _sizeofInt16; } return result; } /// Write the given list of unsigned 16-bit integer [values]. int writeListUint16(List values) { assert(!_inVTable); _prepare(_sizeofUint32, 1, additionalBytes: 2 * values.length); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setUint16AtTail(tail, value); tail -= _sizeofUint16; } return result; } /// Write the given list of bools as unsigend 8-bit integer [values]. int writeListBool(List values) { return writeListUint8(values.map((b) => b ? 1 : 0).toList()); } /// Write the given list of signed 8-bit integer [values]. int writeListInt8(List values) { assert(!_inVTable); _prepare(_sizeofUint32, 1, additionalBytes: values.length); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setInt8AtTail(tail, value); tail -= _sizeofUint8; } return result; } /// Write the given list of unsigned 8-bit integer [values]. int writeListUint8(List values) { assert(!_inVTable); _prepare(_sizeofUint32, 1, additionalBytes: values.length); final result = _tail; var tail = _tail; _setUint32AtTail(tail, values.length); tail -= _sizeofUint32; for (final value in values) { _setUint8AtTail(tail, value); tail -= _sizeofUint8; } return result; } /// Write the given string [value] and return its offset. /// /// Dart strings are UTF-16 but must be stored as UTF-8 in FlatBuffers. /// If the given string consists only of ASCII characters, you can indicate /// enable [asciiOptimization]. In this mode, [writeString()] first tries to /// copy the ASCII string directly to the output buffer and if that fails /// (because there are no-ASCII characters in the string) it falls back and to /// the default UTF-16 -> UTF-8 conversion (with slight performance penalty). int writeString(String value, {bool asciiOptimization = false}) { assert(!_inVTable); if (_strings != null) { return _strings! .putIfAbsent(value, () => _writeString(value, asciiOptimization)); } else { return _writeString(value, asciiOptimization); } } int _writeString(String value, bool asciiOptimization) { if (asciiOptimization) { // [utf8.encode()] is slow (up to at least Dart SDK 2.13). If the given // string is ASCII we can just write it directly, without any conversion. final originalTail = _tail; if (_tryWriteASCIIString(value)) return _tail; // if non-ASCII: reset the output buffer position for [_writeUTFString()] _tail = originalTail; } _writeUTFString(value); return _tail; } // Try to write the string as ASCII, return false if there's a non-ascii char. @pragma('vm:prefer-inline') bool _tryWriteASCIIString(String value) { _prepare(4, 1, additionalBytes: value.length + 1); final length = value.length; var offset = _buf.lengthInBytes - _tail + 4; for (var i = 0; i < length; i++) { // utf16 code unit, e.g. for '†' it's [0x20 0x20], which is 8224 decimal. // ASCII characters go from 0x00 to 0x7F (which is 0 to 127 decimal). final char = value.codeUnitAt(i); if ((char & ~0x7F) != 0) { return false; } _buf.setUint8(offset++, char); } _buf.setUint8(offset, 0); // trailing zero _setUint32AtTail(_tail, value.length); return true; } @pragma('vm:prefer-inline') void _writeUTFString(String value) { final bytes = utf8.encode(value) as Uint8List; final length = bytes.length; _prepare(4, 1, additionalBytes: length + 1); _setUint32AtTail(_tail, length); var offset = _buf.lengthInBytes - _tail + 4; for (var i = 0; i < length; i++) { _buf.setUint8(offset++, bytes[i]); } _buf.setUint8(offset, 0); // trailing zero } /// Used to assert whether a "Table" is currently being built. /// /// If you hit `assert(!_inVTable())`, you're trying to add table fields /// without starting a table with [Builder.startTable()]. /// /// If you hit `assert(_inVTable())`, you're trying to construct a /// Table/Vector/String during the construction of its parent table, /// between the MyTableBuilder and [Builder.endTable()]. /// Move the creation of these sub-objects to before the MyTableBuilder to /// not get this assert. @pragma('vm:prefer-inline') bool get _inVTable => _currentVTable != null; /// The number of bytes that have been written to the buffer so far. The /// most recently written byte is this many bytes from the end of the buffer. @pragma('vm:prefer-inline') int get offset => _tail; /// Zero-pads the buffer, which may be required for some struct layouts. @pragma('vm:prefer-inline') void pad(int howManyBytes) { for (var i = 0; i < howManyBytes; i++) { putUint8(0); } } /// Prepare for writing the given `count` of scalars of the given `size`. /// Additionally allocate the specified `additionalBytes`. Update the current /// tail pointer to point at the allocated space. @pragma('vm:prefer-inline') void _prepare(int size, int count, {int additionalBytes = 0}) { assert(!_finished); // Update the alignment. if (_maxAlign < size) { _maxAlign = size; } // Prepare amount of required space. final dataSize = size * count + additionalBytes; final alignDelta = (-(_tail + dataSize)) & (size - 1); final bufSize = alignDelta + dataSize; // Ensure that we have the required amount of space. { final oldCapacity = _buf.lengthInBytes; if (_tail + bufSize > oldCapacity) { final desiredNewCapacity = (oldCapacity + bufSize) * 2; var deltaCapacity = desiredNewCapacity - oldCapacity; deltaCapacity += (-deltaCapacity) & (_maxAlign - 1); final newCapacity = oldCapacity + deltaCapacity; _buf = _allocator.resize(_buf, newCapacity, _tail, 0); } } // zero out the added padding for (var i = _tail + 1; i <= _tail + alignDelta; i++) { _setUint8AtTail(i, 0); } // Update the tail pointer. _tail += bufSize; } /// Record the offset of the given [field]. @pragma('vm:prefer-inline') void _trackField(int field) => _currentVTable!.addField(field, _tail); @pragma('vm:prefer-inline') void _setFloat64AtTail(int tail, double x) => _buf.setFloat64(_buf.lengthInBytes - tail, x, Endian.little); @pragma('vm:prefer-inline') void _setFloat32AtTail(int tail, double x) => _buf.setFloat32(_buf.lengthInBytes - tail, x, Endian.little); @pragma('vm:prefer-inline') void _setUint64AtTail(int tail, int x) => _buf.setUint64(_buf.lengthInBytes - tail, x, Endian.little); @pragma('vm:prefer-inline') void _setInt64AtTail(int tail, int x) => _buf.setInt64(_buf.lengthInBytes - tail, x, Endian.little); @pragma('vm:prefer-inline') void _setInt32AtTail(int tail, int x) => _buf.setInt32(_buf.lengthInBytes - tail, x, Endian.little); @pragma('vm:prefer-inline') void _setUint32AtTail(int tail, int x) => _buf.setUint32(_buf.lengthInBytes - tail, x, Endian.little); @pragma('vm:prefer-inline') void _setInt16AtTail(int tail, int x) => _buf.setInt16(_buf.lengthInBytes - tail, x, Endian.little); @pragma('vm:prefer-inline') void _setUint16AtTail(int tail, int x) => _buf.setUint16(_buf.lengthInBytes - tail, x, Endian.little); @pragma('vm:prefer-inline') void _setInt8AtTail(int tail, int x) => _buf.setInt8(_buf.lengthInBytes - tail, x); @pragma('vm:prefer-inline') void _setUint8AtTail(int tail, int x) => _buf.setUint8(_buf.lengthInBytes - tail, x); } /// Reader of lists of boolean values. /// /// The returned unmodifiable lists lazily read values on access. class BoolListReader extends Reader> { const BoolListReader(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint32; @override @pragma('vm:prefer-inline') List read(BufferContext bc, int offset) => _FbBoolList(bc, bc.derefObject(offset)); } /// The reader of booleans. class BoolReader extends Reader { const BoolReader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint8; @override @pragma('vm:prefer-inline') bool read(BufferContext bc, int offset) => bc._getInt8(offset) != 0; } /// The reader of lists of 64-bit float values. /// /// The returned unmodifiable lists lazily read values on access. class Float64ListReader extends Reader> { const Float64ListReader(); @override @pragma('vm:prefer-inline') int get size => _sizeofFloat64; @override @pragma('vm:prefer-inline') List read(BufferContext bc, int offset) => _FbFloat64List(bc, bc.derefObject(offset)); } class Float32ListReader extends Reader> { const Float32ListReader(); @override @pragma('vm:prefer-inline') int get size => _sizeofFloat32; @override @pragma('vm:prefer-inline') List read(BufferContext bc, int offset) => _FbFloat32List(bc, bc.derefObject(offset)); } class Float64Reader extends Reader { const Float64Reader(); @override @pragma('vm:prefer-inline') int get size => _sizeofFloat64; @override @pragma('vm:prefer-inline') double read(BufferContext bc, int offset) => bc._getFloat64(offset); } class Float32Reader extends Reader { const Float32Reader(); @override @pragma('vm:prefer-inline') int get size => _sizeofFloat32; @override @pragma('vm:prefer-inline') double read(BufferContext bc, int offset) => bc._getFloat32(offset); } class Int64Reader extends Reader { const Int64Reader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofInt64; @override @pragma('vm:prefer-inline') int read(BufferContext bc, int offset) => bc._getInt64(offset); } /// The reader of signed 32-bit integers. class Int32Reader extends Reader { const Int32Reader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofInt32; @override @pragma('vm:prefer-inline') int read(BufferContext bc, int offset) => bc._getInt32(offset); } /// The reader of signed 32-bit integers. class Int16Reader extends Reader { const Int16Reader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofInt16; @override @pragma('vm:prefer-inline') int read(BufferContext bc, int offset) => bc._getInt16(offset); } /// The reader of 8-bit signed integers. class Int8Reader extends Reader { const Int8Reader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofInt8; @override @pragma('vm:prefer-inline') int read(BufferContext bc, int offset) => bc._getInt8(offset); } /// The reader of lists of objects. Lazy by default - see [lazy]. class ListReader extends Reader> { final Reader _elementReader; /// Enables lazy reading of the list /// /// If true, the returned unmodifiable list lazily reads objects on access. /// Therefore, the underlying buffer must not change while accessing the list. /// /// If false, reads the whole list immediately on access. final bool lazy; const ListReader(this._elementReader, {this.lazy = true}); @override @pragma('vm:prefer-inline') int get size => _sizeofUint32; @override List read(BufferContext bc, int offset) { final listOffset = bc.derefObject(offset); return lazy ? _FbGenericList(_elementReader, bc, listOffset) : List.generate( bc.buffer.getUint32(listOffset, Endian.little), (int index) => _elementReader.read( bc, listOffset + size + _elementReader.size * index), growable: true); } } /// Object that can read a value at a [BufferContext]. abstract class Reader { const Reader(); /// The size of the value in bytes. int get size; /// Read the value at the given [offset] in [bc]. T read(BufferContext bc, int offset); /// Read the value of the given [field] in the given [object]. @pragma('vm:prefer-inline') T vTableGet(BufferContext object, int offset, int field, T defaultValue) { final fieldOffset = _vTableFieldOffset(object, offset, field); return fieldOffset == 0 ? defaultValue : read(object, offset + fieldOffset); } /// Read the value of the given [field] in the given [object]. @pragma('vm:prefer-inline') T? vTableGetNullable(BufferContext object, int offset, int field) { final fieldOffset = _vTableFieldOffset(object, offset, field); return fieldOffset == 0 ? null : read(object, offset + fieldOffset); } @pragma('vm:prefer-inline') int _vTableFieldOffset(BufferContext object, int offset, int field) { final vTableSOffset = object._getInt32(offset); final vTableOffset = offset - vTableSOffset; final vTableSize = object._getUint16(vTableOffset); if (field >= vTableSize) return 0; return object._getUint16(vTableOffset + field); } } /// The reader of string values. class StringReader extends Reader { final bool asciiOptimization; const StringReader({this.asciiOptimization = false}) : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint32; @override @pragma('vm:prefer-inline') String read(BufferContext bc, int offset) { final strOffset = bc.derefObject(offset); final length = bc._getUint32(strOffset); final bytes = bc._asUint8List(strOffset + _sizeofUint32, length); if (asciiOptimization && _isLatin(bytes)) { return String.fromCharCodes(bytes); } return utf8.decode(bytes); } @pragma('vm:prefer-inline') static bool _isLatin(Uint8List bytes) { final length = bytes.length; for (var i = 0; i < length; i++) { if (bytes[i] > 127) { return false; } } return true; } } /// An abstract reader for structs. abstract class StructReader extends Reader { const StructReader(); /// Return the object at `offset`. T createObject(BufferContext bc, int offset); @override T read(BufferContext bc, int offset) { return createObject(bc, offset); } } /// An abstract reader for tables. abstract class TableReader extends Reader { const TableReader(); @override @pragma('vm:prefer-inline') int get size => 4; /// Return the object at [offset]. T createObject(BufferContext bc, int offset); @override T read(BufferContext bc, int offset) { final objectOffset = bc.derefObject(offset); return createObject(bc, objectOffset); } } /// Reader of lists of unsigned 32-bit integer values. /// /// The returned unmodifiable lists lazily read values on access. class Uint32ListReader extends Reader> { const Uint32ListReader(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint32; @override @pragma('vm:prefer-inline') List read(BufferContext bc, int offset) => _FbUint32List(bc, bc.derefObject(offset)); } /// The reader of unsigned 64-bit integers. /// /// WARNING: May have compatibility issues with JavaScript class Uint64Reader extends Reader { const Uint64Reader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint64; @override @pragma('vm:prefer-inline') int read(BufferContext bc, int offset) => bc._getUint64(offset); } /// The reader of unsigned 32-bit integers. class Uint32Reader extends Reader { const Uint32Reader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint32; @override @pragma('vm:prefer-inline') int read(BufferContext bc, int offset) => bc._getUint32(offset); } /// Reader of lists of unsigned 32-bit integer values. /// /// The returned unmodifiable lists lazily read values on access. class Uint16ListReader extends Reader> { const Uint16ListReader(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint32; @override @pragma('vm:prefer-inline') List read(BufferContext bc, int offset) => _FbUint16List(bc, bc.derefObject(offset)); } /// The reader of unsigned 32-bit integers. class Uint16Reader extends Reader { const Uint16Reader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint16; @override @pragma('vm:prefer-inline') int read(BufferContext bc, int offset) => bc._getUint16(offset); } /// Reader of unmodifiable binary data (a list of unsigned 8-bit integers). class Uint8ListReader extends Reader> { /// Enables lazy reading of the list /// /// If true, the returned unmodifiable list lazily reads bytes on access. /// Therefore, the underlying buffer must not change while accessing the list. /// /// If false, reads the whole list immediately as an Uint8List. final bool lazy; const Uint8ListReader({this.lazy = true}); @override @pragma('vm:prefer-inline') int get size => _sizeofUint32; @override @pragma('vm:prefer-inline') List read(BufferContext bc, int offset) { final listOffset = bc.derefObject(offset); if (lazy) return _FbUint8List(bc, listOffset); final length = bc._getUint32(listOffset); final result = Uint8List(length); var pos = listOffset + _sizeofUint32; for (var i = 0; i < length; i++, pos++) { result[i] = bc._getUint8(pos); } return result; } } /// The reader of unsigned 8-bit integers. class Uint8Reader extends Reader { const Uint8Reader() : super(); @override @pragma('vm:prefer-inline') int get size => _sizeofUint8; @override @pragma('vm:prefer-inline') int read(BufferContext bc, int offset) => bc._getUint8(offset); } /// Reader of unmodifiable binary data (a list of signed 8-bit integers). class Int8ListReader extends Reader> { /// Enables lazy reading of the list /// /// If true, the returned unmodifiable list lazily reads bytes on access. /// Therefore, the underlying buffer must not change while accessing the list. /// /// If false, reads the whole list immediately as an Uint8List. final bool lazy; const Int8ListReader({this.lazy = true}); @override @pragma('vm:prefer-inline') int get size => _sizeofUint32; @override @pragma('vm:prefer-inline') List read(BufferContext bc, int offset) { final listOffset = bc.derefObject(offset); if (lazy) return _FbUint8List(bc, listOffset); final length = bc._getUint32(listOffset); final result = Int8List(length); var pos = listOffset + _sizeofUint32; for (var i = 0; i < length; i++, pos++) { result[i] = bc._getInt8(pos); } return result; } } /// The list backed by 64-bit values - Uint64 length and Float64. class _FbFloat64List extends _FbList { _FbFloat64List(BufferContext bc, int offset) : super(bc, offset); @override @pragma('vm:prefer-inline') double operator [](int i) => bc._getFloat64(offset + 4 + 8 * i); } /// The list backed by 32-bit values - Float32. class _FbFloat32List extends _FbList { _FbFloat32List(BufferContext bc, int offset) : super(bc, offset); @override @pragma('vm:prefer-inline') double operator [](int i) => bc._getFloat32(offset + 4 + 4 * i); } /// List backed by a generic object which may have any size. class _FbGenericList extends _FbList { final Reader elementReader; List? _items; _FbGenericList(this.elementReader, BufferContext bp, int offset) : super(bp, offset); @override @pragma('vm:prefer-inline') E operator [](int i) { _items ??= List.filled(length, null); var item = _items![i]; if (item == null) { item = elementReader.read(bc, offset + 4 + elementReader.size * i); _items![i] = item; } return item!; } } /// The base class for immutable lists read from flat buffers. abstract class _FbList extends Object with ListMixin implements List { final BufferContext bc; final int offset; int? _length; _FbList(this.bc, this.offset); @override @pragma('vm:prefer-inline') int get length => _length ??= bc._getUint32(offset); @override set length(int i) => throw StateError('Attempt to modify immutable list'); @override void operator []=(int i, E e) => throw StateError('Attempt to modify immutable list'); } /// List backed by 32-bit unsigned integers. class _FbUint32List extends _FbList { _FbUint32List(BufferContext bc, int offset) : super(bc, offset); @override @pragma('vm:prefer-inline') int operator [](int i) => bc._getUint32(offset + 4 + 4 * i); } /// List backed by 16-bit unsigned integers. class _FbUint16List extends _FbList { _FbUint16List(BufferContext bc, int offset) : super(bc, offset); @override @pragma('vm:prefer-inline') int operator [](int i) => bc._getUint16(offset + 4 + 2 * i); } /// List backed by 8-bit unsigned integers. class _FbUint8List extends _FbList { _FbUint8List(BufferContext bc, int offset) : super(bc, offset); @override @pragma('vm:prefer-inline') int operator [](int i) => bc._getUint8(offset + 4 + i); } /// List backed by 8-bit signed integers. class _FbInt8List extends _FbList { _FbInt8List(BufferContext bc, int offset) : super(bc, offset); @override @pragma('vm:prefer-inline') int operator [](int i) => bc._getInt8(offset + 4 + i); } /// List backed by 8-bit unsigned integers. class _FbBoolList extends _FbList { _FbBoolList(BufferContext bc, int offset) : super(bc, offset); @override @pragma('vm:prefer-inline') bool operator [](int i) => bc._getUint8(offset + 4 + i) == 1 ? true : false; } /// Class that describes the structure of a table. class _VTable { static const int _metadataLength = 4; final int numFields; // Note: fieldOffsets start as "tail offsets" and are then transformed by // [computeFieldOffsets()] to actual offsets when a table is finished. final Uint32List fieldOffsets; bool offsetsComputed = false; _VTable(this.numFields) : fieldOffsets = Uint32List(numFields); /// The size of the table that uses this VTable. int tableSize = 0; /// The tail of this VTable. It is used to share the same VTable between /// multiple tables of identical structure. int tail = 0; int get _vTableSize => numOfUint16 * _sizeofUint16; int get numOfUint16 => 1 + 1 + numFields; @pragma('vm:prefer-inline') void addField(int field, int offset) { assert(!offsetsComputed); assert(offset > 0); // it's impossible for field to start at the buffer end assert(offset <= 4294967295); // uint32 max fieldOffsets[field] = offset; } @pragma('vm:prefer-inline') bool _offsetsMatch(int vt2Start, ByteData buf) { assert(offsetsComputed); for (var i = 0; i < numFields; i++) { if (fieldOffsets[i] != buf.getUint16(vt2Start + _metadataLength + (2 * i), Endian.little)) { return false; } } return true; } /// Fill the [fieldOffsets] field. @pragma('vm:prefer-inline') void computeFieldOffsets(int tableTail) { assert(!offsetsComputed); offsetsComputed = true; for (var i = 0; i < numFields; i++) { if (fieldOffsets[i] != 0) { fieldOffsets[i] = tableTail - fieldOffsets[i]; } } } /// Outputs this VTable to [buf], which is is expected to be aligned to 16-bit /// and have at least [numOfUint16] 16-bit words available. @pragma('vm:prefer-inline') void output(ByteData buf, int bufOffset) { assert(offsetsComputed); // VTable size. buf.setUint16(bufOffset, numOfUint16 * 2, Endian.little); bufOffset += 2; // Table size. buf.setUint16(bufOffset, tableSize, Endian.little); bufOffset += 2; // Field offsets. for (var i = 0; i < numFields; i++) { buf.setUint16(bufOffset, fieldOffsets[i], Endian.little); bufOffset += 2; } } } /// The interface that [Builder] uses to allocate buffers for encoding. abstract class Allocator { const Allocator(); /// Allocate a [ByteData] buffer of a given size. ByteData allocate(int size); /// Free the given [ByteData] buffer previously allocated by [allocate]. void deallocate(ByteData data); /// Reallocate [newSize] bytes of memory, replacing the old [oldData]. This /// grows downwards, and is intended specifically for use with [Builder]. /// Params [inUseBack] and [inUseFront] indicate how much of [oldData] is /// actually in use at each end, and needs to be copied. ByteData resize( ByteData oldData, int newSize, int inUseBack, int inUseFront) { final newData = allocate(newSize); _copyDownward(oldData, newData, inUseBack, inUseFront); deallocate(oldData); return newData; } /// Called by [resize] to copy memory from [oldData] to [newData]. Only /// memory of size [inUseFront] and [inUseBack] will be copied from the front /// and back of the old memory allocation. void _copyDownward( ByteData oldData, ByteData newData, int inUseBack, int inUseFront) { if (inUseBack != 0) { newData.buffer.asUint8List().setAll( newData.lengthInBytes - inUseBack, oldData.buffer.asUint8List().getRange( oldData.lengthInBytes - inUseBack, oldData.lengthInBytes)); } if (inUseFront != 0) { newData.buffer .asUint8List() .setAll(0, oldData.buffer.asUint8List().getRange(0, inUseFront)); } } } class DefaultAllocator extends Allocator { const DefaultAllocator(); @override ByteData allocate(int size) => ByteData(size); @override void deallocate(ByteData data) { // nothing to do, it's garbage-collected } }