677 lines
22 KiB
Dart
677 lines
22 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'types.dart';
|
|
|
|
/// The main builder class for creation of a FlexBuffer.
|
|
class Builder {
|
|
final ByteData _buffer;
|
|
List<_StackValue> _stack = [];
|
|
List<_StackPointer> _stackPointers = [];
|
|
int _offset = 0;
|
|
bool _finished = false;
|
|
final Map<String, _StackValue> _stringCache = {};
|
|
final Map<String, _StackValue> _keyCache = {};
|
|
final Map<_KeysHash, _StackValue> _keyVectorCache = {};
|
|
final Map<int, _StackValue> _indirectIntCache = {};
|
|
final Map<double, _StackValue> _indirectDoubleCache = {};
|
|
|
|
/// Instantiate the builder if you intent to gradually build up the buffer by calling
|
|
/// add... methods and calling [finish] to receive the resulting byte array.
|
|
///
|
|
/// The default size of internal buffer is set to 2048. Provide a different value in order to avoid buffer copies.
|
|
Builder({int size = 2048}) : _buffer = ByteData(size);
|
|
|
|
/// Use this method in order to turn an object into a FlexBuffer directly.
|
|
///
|
|
/// Use the manual instantiation of the [Builder] and gradual addition of values, if performance is more important than convenience.
|
|
static ByteBuffer buildFromObject(Object? value) {
|
|
final builder = Builder();
|
|
builder._add(value);
|
|
final buffer = builder.finish();
|
|
final byteData = ByteData(buffer.lengthInBytes);
|
|
byteData.buffer.asUint8List().setAll(0, buffer);
|
|
return byteData.buffer;
|
|
}
|
|
|
|
void _add(Object? value) {
|
|
if (value == null) {
|
|
addNull();
|
|
} else if (value is bool) {
|
|
addBool(value);
|
|
} else if (value is int) {
|
|
addInt(value);
|
|
} else if (value is double) {
|
|
addDouble(value);
|
|
} else if (value is ByteBuffer) {
|
|
addBlob(value);
|
|
} else if (value is String) {
|
|
addString(value);
|
|
} else if (value is List<dynamic>) {
|
|
startVector();
|
|
for (var i = 0; i < value.length; i++) {
|
|
_add(value[i]);
|
|
}
|
|
end();
|
|
} else if (value is Map<String, dynamic>) {
|
|
startMap();
|
|
value.forEach((key, value) {
|
|
addKey(key);
|
|
_add(value);
|
|
});
|
|
end();
|
|
} else {
|
|
throw UnsupportedError('Value of unexpected type: $value');
|
|
}
|
|
}
|
|
|
|
/// Use this method if you want to store a null value.
|
|
///
|
|
/// Specifically useful when building up a vector where values can be null.
|
|
void addNull() {
|
|
_integrityCheckOnValueAddition();
|
|
_stack.add(_StackValue.withNull());
|
|
}
|
|
|
|
/// Adds a string value.
|
|
void addInt(int value) {
|
|
_integrityCheckOnValueAddition();
|
|
_stack.add(_StackValue.withInt(value));
|
|
}
|
|
|
|
/// Adds a bool value.
|
|
void addBool(bool value) {
|
|
_integrityCheckOnValueAddition();
|
|
_stack.add(_StackValue.withBool(value));
|
|
}
|
|
|
|
/// Adds a double value.
|
|
void addDouble(double value) {
|
|
_integrityCheckOnValueAddition();
|
|
_stack.add(_StackValue.withDouble(value));
|
|
}
|
|
|
|
/// Adds a string value.
|
|
void addString(String value) {
|
|
_integrityCheckOnValueAddition();
|
|
if (_stringCache.containsKey(value)) {
|
|
_stack.add(_stringCache[value]!);
|
|
return;
|
|
}
|
|
final utf8String = utf8.encode(value);
|
|
final length = utf8String.length;
|
|
final bitWidth = BitWidthUtil.uwidth(length);
|
|
final byteWidth = _align(bitWidth);
|
|
_writeUInt(length, byteWidth);
|
|
final stringOffset = _offset;
|
|
final newOffset = _newOffset(length + 1);
|
|
_pushBuffer(utf8String);
|
|
_offset = newOffset;
|
|
final stackValue =
|
|
_StackValue.withOffset(stringOffset, ValueType.String, bitWidth);
|
|
_stack.add(stackValue);
|
|
_stringCache[value] = stackValue;
|
|
}
|
|
|
|
/// This methods adds a key to a map and should be followed by an add... value call.
|
|
///
|
|
/// It also implies that you call this method only after you called [startMap].
|
|
void addKey(String value) {
|
|
_integrityCheckOnKeyAddition();
|
|
if (_keyCache.containsKey(value)) {
|
|
_stack.add(_keyCache[value]!);
|
|
return;
|
|
}
|
|
final utf8String = utf8.encode(value);
|
|
final length = utf8String.length;
|
|
final keyOffset = _offset;
|
|
final newOffset = _newOffset(length + 1);
|
|
_pushBuffer(utf8String);
|
|
_offset = newOffset;
|
|
final stackValue =
|
|
_StackValue.withOffset(keyOffset, ValueType.Key, BitWidth.width8);
|
|
_stack.add(stackValue);
|
|
_keyCache[value] = stackValue;
|
|
}
|
|
|
|
/// Adds a byte array.
|
|
///
|
|
/// This method can be used to store any generic BLOB.
|
|
void addBlob(ByteBuffer value) {
|
|
_integrityCheckOnValueAddition();
|
|
final length = value.lengthInBytes;
|
|
final bitWidth = BitWidthUtil.uwidth(length);
|
|
final byteWidth = _align(bitWidth);
|
|
_writeUInt(length, byteWidth);
|
|
final blobOffset = _offset;
|
|
final newOffset = _newOffset(length);
|
|
_pushBuffer(value.asUint8List());
|
|
_offset = newOffset;
|
|
final stackValue =
|
|
_StackValue.withOffset(blobOffset, ValueType.Blob, bitWidth);
|
|
_stack.add(stackValue);
|
|
}
|
|
|
|
/// Stores int value indirectly in the buffer.
|
|
///
|
|
/// Adding large integer values indirectly might be beneficial if those values suppose to be store in a vector together with small integer values.
|
|
/// This is due to the fact that FlexBuffers will add padding to small integer values, if they are stored together with large integer values.
|
|
/// When we add integer indirectly the vector of ints will contain not the value itself, but only the relative offset to the value.
|
|
/// By setting the [cache] parameter to true, you make sure that the builder tracks added int value and performs deduplication.
|
|
void addIntIndirectly(int value, {bool cache = false}) {
|
|
_integrityCheckOnValueAddition();
|
|
if (_indirectIntCache.containsKey(value)) {
|
|
_stack.add(_indirectIntCache[value]!);
|
|
return;
|
|
}
|
|
final stackValue = _StackValue.withInt(value);
|
|
final byteWidth = _align(stackValue.width);
|
|
final newOffset = _newOffset(byteWidth);
|
|
final valueOffset = _offset;
|
|
_pushBuffer(stackValue.asU8List(stackValue.width));
|
|
final stackOffset = _StackValue.withOffset(
|
|
valueOffset, ValueType.IndirectInt, stackValue.width);
|
|
_stack.add(stackOffset);
|
|
_offset = newOffset;
|
|
if (cache) {
|
|
_indirectIntCache[value] = stackOffset;
|
|
}
|
|
}
|
|
|
|
/// Stores double value indirectly in the buffer.
|
|
///
|
|
/// Double are stored as 8 or 4 byte values in FlexBuffers. If they are stored in a mixed vector, values which are smaller than 4 / 8 bytes will be padded.
|
|
/// When we add double indirectly, the vector will contain not the value itself, but only the relative offset to the value. Which could occupy only 1 or 2 bytes, reducing the odds for unnecessary padding.
|
|
/// By setting the [cache] parameter to true, you make sure that the builder tracks already added double value and performs deduplication.
|
|
void addDoubleIndirectly(double value, {bool cache = false}) {
|
|
_integrityCheckOnValueAddition();
|
|
if (cache && _indirectDoubleCache.containsKey(value)) {
|
|
_stack.add(_indirectDoubleCache[value]!);
|
|
return;
|
|
}
|
|
final stackValue = _StackValue.withDouble(value);
|
|
final byteWidth = _align(stackValue.width);
|
|
final newOffset = _newOffset(byteWidth);
|
|
final valueOffset = _offset;
|
|
_pushBuffer(stackValue.asU8List(stackValue.width));
|
|
final stackOffset = _StackValue.withOffset(
|
|
valueOffset, ValueType.IndirectFloat, stackValue.width);
|
|
_stack.add(stackOffset);
|
|
_offset = newOffset;
|
|
if (cache) {
|
|
_indirectDoubleCache[value] = stackOffset;
|
|
}
|
|
}
|
|
|
|
/// This method starts a vector definition and needs to be followed by 0 to n add... value calls.
|
|
///
|
|
/// The vector definition needs to be finished with an [end] call.
|
|
/// It is also possible to add nested vector or map by calling [startVector] / [startMap].
|
|
void startVector() {
|
|
_integrityCheckOnValueAddition();
|
|
_stackPointers.add(_StackPointer(_stack.length, true));
|
|
}
|
|
|
|
/// This method starts a map definition.
|
|
///
|
|
/// This method call needs to be followed by 0 to n [addKey] + add... value calls.
|
|
/// The map definition needs to be finished with an [end] call.
|
|
/// It is also possible to add nested vector or map by calling [startVector] / [startMap] after calling [addKey].
|
|
void startMap() {
|
|
_integrityCheckOnValueAddition();
|
|
_stackPointers.add(_StackPointer(_stack.length, false));
|
|
}
|
|
|
|
/// Marks that the addition of values to the last vector, or map have ended.
|
|
void end() {
|
|
final pointer = _stackPointers.removeLast();
|
|
if (pointer.isVector) {
|
|
_endVector(pointer);
|
|
} else {
|
|
_sortKeysAndEndMap(pointer);
|
|
}
|
|
}
|
|
|
|
/// Finish building the FlatBuffer and return array of bytes.
|
|
///
|
|
/// Can be called multiple times, to get the array of bytes.
|
|
/// After the first call, adding values, or starting vectors / maps will result in an exception.
|
|
Uint8List finish() {
|
|
if (_finished == false) {
|
|
_finish();
|
|
}
|
|
return _buffer.buffer.asUint8List(0, _offset);
|
|
}
|
|
|
|
/// Builds a FlatBuffer with current state without finishing the builder.
|
|
///
|
|
/// Creates an internal temporary copy of current builder and finishes the copy.
|
|
/// Use this method, when the state of a long lasting builder need to be persisted periodically.
|
|
ByteBuffer snapshot() {
|
|
final tmp = Builder(size: _offset + 200);
|
|
tmp._offset = _offset;
|
|
tmp._stack = List.from(_stack);
|
|
tmp._stackPointers = List.from(_stackPointers);
|
|
tmp._buffer.buffer
|
|
.asUint8List()
|
|
.setAll(0, _buffer.buffer.asUint8List(0, _offset));
|
|
for (var i = 0; i < tmp._stackPointers.length; i++) {
|
|
tmp.end();
|
|
}
|
|
final buffer = tmp.finish();
|
|
final bd = ByteData(buffer.lengthInBytes);
|
|
bd.buffer.asUint8List().setAll(0, buffer);
|
|
return bd.buffer;
|
|
}
|
|
|
|
void _integrityCheckOnValueAddition() {
|
|
if (_finished) {
|
|
throw StateError('Adding values after finish is prohibited');
|
|
}
|
|
if (_stackPointers.isNotEmpty && _stackPointers.last.isVector == false) {
|
|
if (_stack.last.type != ValueType.Key) {
|
|
throw StateError(
|
|
'Adding value to a map before adding a key is prohibited');
|
|
}
|
|
}
|
|
}
|
|
|
|
void _integrityCheckOnKeyAddition() {
|
|
if (_finished) {
|
|
throw StateError('Adding values after finish is prohibited');
|
|
}
|
|
if (_stackPointers.isEmpty || _stackPointers.last.isVector) {
|
|
throw StateError('Adding key before staring a map is prohibited');
|
|
}
|
|
}
|
|
|
|
void _finish() {
|
|
if (_stack.length != 1) {
|
|
throw StateError(
|
|
'Stack has to be exactly 1, but is ${_stack.length}. You have to end all started vectors and maps, before calling [finish]');
|
|
}
|
|
final value = _stack[0];
|
|
final byteWidth = _align(value.elementWidth(_offset, 0));
|
|
_writeStackValue(value, byteWidth);
|
|
_writeUInt(value.storedPackedType(), 1);
|
|
_writeUInt(byteWidth, 1);
|
|
_finished = true;
|
|
}
|
|
|
|
_StackValue _createVector(int start, int vecLength, int step,
|
|
[_StackValue? keys]) {
|
|
var bitWidth = BitWidthUtil.uwidth(vecLength);
|
|
var prefixElements = 1;
|
|
if (keys != null) {
|
|
var elemWidth = keys.elementWidth(_offset, 0);
|
|
if (elemWidth.index > bitWidth.index) {
|
|
bitWidth = elemWidth;
|
|
}
|
|
prefixElements += 2;
|
|
}
|
|
var vectorType = ValueType.Key;
|
|
var typed = keys == null;
|
|
for (var i = start; i < _stack.length; i += step) {
|
|
final elemWidth = _stack[i].elementWidth(_offset, i + prefixElements);
|
|
if (elemWidth.index > bitWidth.index) {
|
|
bitWidth = elemWidth;
|
|
}
|
|
if (i == start) {
|
|
vectorType = _stack[i].type;
|
|
typed &= ValueTypeUtils.isTypedVectorElement(vectorType);
|
|
} else {
|
|
if (vectorType != _stack[i].type) {
|
|
typed = false;
|
|
}
|
|
}
|
|
}
|
|
final byteWidth = _align(bitWidth);
|
|
final fix = typed & ValueTypeUtils.isNumber(vectorType) &&
|
|
vecLength >= 2 &&
|
|
vecLength <= 4;
|
|
if (keys != null) {
|
|
_writeStackValue(keys, byteWidth);
|
|
_writeUInt(1 << keys.width.index, byteWidth);
|
|
}
|
|
if (fix == false) {
|
|
_writeUInt(vecLength, byteWidth);
|
|
}
|
|
final vecOffset = _offset;
|
|
for (var i = start; i < _stack.length; i += step) {
|
|
_writeStackValue(_stack[i], byteWidth);
|
|
}
|
|
if (typed == false) {
|
|
for (var i = start; i < _stack.length; i += step) {
|
|
_writeUInt(_stack[i].storedPackedType(), 1);
|
|
}
|
|
}
|
|
if (keys != null) {
|
|
return _StackValue.withOffset(vecOffset, ValueType.Map, bitWidth);
|
|
}
|
|
if (typed) {
|
|
final vType =
|
|
ValueTypeUtils.toTypedVector(vectorType, fix ? vecLength : 0);
|
|
return _StackValue.withOffset(vecOffset, vType, bitWidth);
|
|
}
|
|
return _StackValue.withOffset(vecOffset, ValueType.Vector, bitWidth);
|
|
}
|
|
|
|
void _endVector(_StackPointer pointer) {
|
|
final vecLength = _stack.length - pointer.stackPosition;
|
|
final vec = _createVector(pointer.stackPosition, vecLength, 1);
|
|
_stack.removeRange(pointer.stackPosition, _stack.length);
|
|
_stack.add(vec);
|
|
}
|
|
|
|
void _sortKeysAndEndMap(_StackPointer pointer) {
|
|
if (((_stack.length - pointer.stackPosition) & 1) == 1) {
|
|
throw StateError(
|
|
'The stack needs to hold key value pairs (even number of elements). Check if you combined [addKey] with add... method calls properly.');
|
|
}
|
|
|
|
var sorted = true;
|
|
for (var i = pointer.stackPosition; i < _stack.length - 2; i += 2) {
|
|
if (_shouldFlip(_stack[i], _stack[i + 2])) {
|
|
sorted = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sorted == false) {
|
|
for (var i = pointer.stackPosition; i < _stack.length; i += 2) {
|
|
var flipIndex = i;
|
|
for (var j = i + 2; j < _stack.length; j += 2) {
|
|
if (_shouldFlip(_stack[flipIndex], _stack[j])) {
|
|
flipIndex = j;
|
|
}
|
|
}
|
|
if (flipIndex != i) {
|
|
var k = _stack[flipIndex];
|
|
var v = _stack[flipIndex + 1];
|
|
_stack[flipIndex] = _stack[i];
|
|
_stack[flipIndex + 1] = _stack[i + 1];
|
|
_stack[i] = k;
|
|
_stack[i + 1] = v;
|
|
}
|
|
}
|
|
}
|
|
_endMap(pointer);
|
|
}
|
|
|
|
void _endMap(_StackPointer pointer) {
|
|
final vecLength = (_stack.length - pointer.stackPosition) >> 1;
|
|
final offsets = <int>[];
|
|
for (var i = pointer.stackPosition; i < _stack.length; i += 2) {
|
|
offsets.add(_stack[i].offset!);
|
|
}
|
|
final keysHash = _KeysHash(offsets);
|
|
_StackValue? keysStackValue;
|
|
if (_keyVectorCache.containsKey(keysHash)) {
|
|
keysStackValue = _keyVectorCache[keysHash];
|
|
} else {
|
|
keysStackValue = _createVector(pointer.stackPosition, vecLength, 2);
|
|
_keyVectorCache[keysHash] = keysStackValue;
|
|
}
|
|
final vec =
|
|
_createVector(pointer.stackPosition + 1, vecLength, 2, keysStackValue);
|
|
_stack.removeRange(pointer.stackPosition, _stack.length);
|
|
_stack.add(vec);
|
|
}
|
|
|
|
bool _shouldFlip(_StackValue v1, _StackValue v2) {
|
|
if (v1.type != ValueType.Key || v2.type != ValueType.Key) {
|
|
throw StateError(
|
|
'Stack values are not keys $v1 | $v2. Check if you combined [addKey] with add... method calls properly.');
|
|
}
|
|
|
|
late int c1, c2;
|
|
var index = 0;
|
|
do {
|
|
c1 = _buffer.getUint8(v1.offset! + index);
|
|
c2 = _buffer.getUint8(v2.offset! + index);
|
|
if (c2 < c1) return true;
|
|
if (c1 < c2) return false;
|
|
index += 1;
|
|
} while (c1 != 0 && c2 != 0);
|
|
return false;
|
|
}
|
|
|
|
int _align(BitWidth width) {
|
|
final byteWidth = BitWidthUtil.toByteWidth(width);
|
|
_offset += BitWidthUtil.paddingSize(_offset, byteWidth);
|
|
return byteWidth;
|
|
}
|
|
|
|
void _writeStackValue(_StackValue value, int byteWidth) {
|
|
final newOffset = _newOffset(byteWidth);
|
|
if (value.isOffset) {
|
|
final relativeOffset = _offset - value.offset!;
|
|
if (byteWidth == 8 || relativeOffset < (1 << (byteWidth * 8))) {
|
|
_writeUInt(relativeOffset, byteWidth);
|
|
} else {
|
|
throw StateError(
|
|
'Unexpected size $byteWidth. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
|
|
}
|
|
} else {
|
|
_pushBuffer(value.asU8List(BitWidthUtil.fromByteWidth(byteWidth)));
|
|
}
|
|
_offset = newOffset;
|
|
}
|
|
|
|
void _writeUInt(int value, int byteWidth) {
|
|
final newOffset = _newOffset(byteWidth);
|
|
_pushUInt(value, BitWidthUtil.fromByteWidth(byteWidth));
|
|
_offset = newOffset;
|
|
}
|
|
|
|
int _newOffset(int newValueSize) {
|
|
final newOffset = _offset + newValueSize;
|
|
var size = _buffer.lengthInBytes;
|
|
final prevSize = size;
|
|
while (size < newOffset) {
|
|
size <<= 1;
|
|
}
|
|
if (prevSize < size) {
|
|
final newBuf = ByteData(size);
|
|
newBuf.buffer.asUint8List().setAll(0, _buffer.buffer.asUint8List());
|
|
}
|
|
return newOffset;
|
|
}
|
|
|
|
void _pushInt(int value, BitWidth width) {
|
|
switch (width) {
|
|
case BitWidth.width8:
|
|
_buffer.setInt8(_offset, value);
|
|
break;
|
|
case BitWidth.width16:
|
|
_buffer.setInt16(_offset, value, Endian.little);
|
|
break;
|
|
case BitWidth.width32:
|
|
_buffer.setInt32(_offset, value, Endian.little);
|
|
break;
|
|
case BitWidth.width64:
|
|
_buffer.setInt64(_offset, value, Endian.little);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _pushUInt(int value, BitWidth width) {
|
|
switch (width) {
|
|
case BitWidth.width8:
|
|
_buffer.setUint8(_offset, value);
|
|
break;
|
|
case BitWidth.width16:
|
|
_buffer.setUint16(_offset, value, Endian.little);
|
|
break;
|
|
case BitWidth.width32:
|
|
_buffer.setUint32(_offset, value, Endian.little);
|
|
break;
|
|
case BitWidth.width64:
|
|
_buffer.setUint64(_offset, value, Endian.little);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _pushBuffer(List<int> value) {
|
|
_buffer.buffer.asUint8List().setAll(_offset, value);
|
|
}
|
|
}
|
|
|
|
class _StackValue {
|
|
late Object _value;
|
|
int? _offset;
|
|
final ValueType _type;
|
|
final BitWidth _width;
|
|
|
|
_StackValue.withNull()
|
|
: _type = ValueType.Null,
|
|
_width = BitWidth.width8;
|
|
|
|
_StackValue.withInt(int value)
|
|
: _type = ValueType.Int,
|
|
_width = BitWidthUtil.width(value),
|
|
_value = value;
|
|
|
|
_StackValue.withBool(bool value)
|
|
: _type = ValueType.Bool,
|
|
_width = BitWidth.width8,
|
|
_value = value;
|
|
|
|
_StackValue.withDouble(double value)
|
|
: _type = ValueType.Float,
|
|
_width = BitWidthUtil.width(value),
|
|
_value = value;
|
|
|
|
_StackValue.withOffset(int value, ValueType type, BitWidth width)
|
|
: _offset = value,
|
|
_type = type,
|
|
_width = width;
|
|
|
|
BitWidth storedWidth({BitWidth width = BitWidth.width8}) {
|
|
return ValueTypeUtils.isInline(_type)
|
|
? BitWidthUtil.max(_width, width)
|
|
: _width;
|
|
}
|
|
|
|
int storedPackedType({BitWidth width = BitWidth.width8}) {
|
|
return ValueTypeUtils.packedType(_type, storedWidth(width: width));
|
|
}
|
|
|
|
BitWidth elementWidth(int size, int index) {
|
|
if (ValueTypeUtils.isInline(_type)) return _width;
|
|
final offset = _offset!;
|
|
for (var i = 0; i < 4; i++) {
|
|
final width = 1 << i;
|
|
final bitWidth = BitWidthUtil.uwidth(size +
|
|
BitWidthUtil.paddingSize(size, width) +
|
|
index * width -
|
|
offset);
|
|
if (1 << bitWidth.index == width) {
|
|
return bitWidth;
|
|
}
|
|
}
|
|
throw StateError(
|
|
'Element is of unknown. Size: $size at index: $index. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
|
|
}
|
|
|
|
List<int> asU8List(BitWidth width) {
|
|
if (ValueTypeUtils.isNumber(_type)) {
|
|
if (_type == ValueType.Float) {
|
|
if (width == BitWidth.width32) {
|
|
final result = ByteData(4);
|
|
result.setFloat32(0, _value as double, Endian.little);
|
|
return result.buffer.asUint8List();
|
|
} else {
|
|
final result = ByteData(8);
|
|
result.setFloat64(0, _value as double, Endian.little);
|
|
return result.buffer.asUint8List();
|
|
}
|
|
} else {
|
|
switch (width) {
|
|
case BitWidth.width8:
|
|
final result = ByteData(1);
|
|
result.setInt8(0, _value as int);
|
|
return result.buffer.asUint8List();
|
|
case BitWidth.width16:
|
|
final result = ByteData(2);
|
|
result.setInt16(0, _value as int, Endian.little);
|
|
return result.buffer.asUint8List();
|
|
case BitWidth.width32:
|
|
final result = ByteData(4);
|
|
result.setInt32(0, _value as int, Endian.little);
|
|
return result.buffer.asUint8List();
|
|
case BitWidth.width64:
|
|
final result = ByteData(8);
|
|
result.setInt64(0, _value as int, Endian.little);
|
|
return result.buffer.asUint8List();
|
|
}
|
|
}
|
|
}
|
|
if (_type == ValueType.Null) {
|
|
final result = ByteData(1);
|
|
result.setInt8(0, 0);
|
|
return result.buffer.asUint8List();
|
|
}
|
|
if (_type == ValueType.Bool) {
|
|
final result = ByteData(1);
|
|
result.setInt8(0, _value as bool ? 1 : 0);
|
|
return result.buffer.asUint8List();
|
|
}
|
|
|
|
throw StateError(
|
|
'Unexpected type: $_type. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
|
|
}
|
|
|
|
ValueType get type {
|
|
return _type;
|
|
}
|
|
|
|
BitWidth get width {
|
|
return _width;
|
|
}
|
|
|
|
bool get isOffset {
|
|
return !ValueTypeUtils.isInline(_type);
|
|
}
|
|
|
|
int? get offset => _offset;
|
|
|
|
bool get isFloat32 {
|
|
return _type == ValueType.Float && _width == BitWidth.width32;
|
|
}
|
|
}
|
|
|
|
class _StackPointer {
|
|
int stackPosition;
|
|
bool isVector;
|
|
|
|
_StackPointer(this.stackPosition, this.isVector);
|
|
}
|
|
|
|
class _KeysHash {
|
|
final List<int> keys;
|
|
|
|
const _KeysHash(this.keys);
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (other is _KeysHash) {
|
|
if (keys.length != other.keys.length) return false;
|
|
for (var i = 0; i < keys.length; i++) {
|
|
if (keys[i] != other.keys[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
var result = 17;
|
|
for (var i = 0; i < keys.length; i++) {
|
|
result = result * 23 + keys[i];
|
|
}
|
|
return result;
|
|
}
|
|
}
|