diff options
author | Maxim Zaks <maxim.zaks@gmail.com> | 2020-05-18 16:50:39 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-18 07:50:39 -0700 |
commit | 5aa443d98c79f5807fed5ea14d18623532f4dd80 (patch) | |
tree | 653229451531df0fe11fa6e703af4f1e00a25100 /dart | |
parent | 0fa087657eccd6ed63386322d6aaf01a098d99ea (diff) | |
download | flatbuffers-5aa443d98c79f5807fed5ea14d18623532f4dd80.tar.gz flatbuffers-5aa443d98c79f5807fed5ea14d18623532f4dd80.tar.bz2 flatbuffers-5aa443d98c79f5807fed5ea14d18623532f4dd80.zip |
[Dart] Adding FlexBuffers support (#5853)
* Adding FlexBuffers support for Dart language
Diffstat (limited to 'dart')
-rw-r--r-- | dart/lib/flex_buffers.dart | 2 | ||||
-rw-r--r-- | dart/lib/src/builder.dart | 648 | ||||
-rw-r--r-- | dart/lib/src/reference.dart | 446 | ||||
-rw-r--r-- | dart/lib/src/types.dart | 156 | ||||
-rw-r--r-- | dart/test/flex_builder_test.dart | 315 | ||||
-rw-r--r-- | dart/test/flex_reader_test.dart | 316 | ||||
-rw-r--r-- | dart/test/flex_types_test.dart | 137 |
7 files changed, 2020 insertions, 0 deletions
diff --git a/dart/lib/flex_buffers.dart b/dart/lib/flex_buffers.dart new file mode 100644 index 00000000..824affff --- /dev/null +++ b/dart/lib/flex_buffers.dart @@ -0,0 +1,2 @@ +export 'src/builder.dart'; +export 'src/reference.dart'; diff --git a/dart/lib/src/builder.dart b/dart/lib/src/builder.dart new file mode 100644 index 00000000..afdf7232 --- /dev/null +++ b/dart/lib/src/builder.dart @@ -0,0 +1,648 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'types.dart'; + +/// The main builder class for creation of a FlexBuffer. +class Builder { + ByteData _buffer; + List<_StackValue> _stack; + List<_StackPointer> _stackPointers; + int _offset; + bool _finished; + Map<String, _StackValue> _stringCache; + Map<String, _StackValue> _keyCache; + Map<_KeysHash, _StackValue> _keyVectorCache; + Map<int, _StackValue> _indirectIntCache; + 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 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); + _stack = []; + _stackPointers = []; + _offset = 0; + _finished = false; + _stringCache = {}; + _keyCache = {}; + _keyVectorCache = {}; + _indirectIntCache = {}; + _indirectDoubleCache = {}; + } + + /// 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.width(length); + final byteWidth = _align(bitWidth); + _writeInt(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.width(length); + final byteWidth = _align(bitWidth); + _writeInt(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); + _writeInt(value.storedPackedType(), 1); + _writeInt(byteWidth, 1); + _finished = true; + } + + _StackValue _createVector(int start, int vecLength, int step, [_StackValue keys]) { + var bitWidth = BitWidthUtil.width(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); + _writeInt(1 << keys.width.index, byteWidth); + } + if (fix == false) { + _writeInt(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) { + _writeInt(_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); + var 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.'); + } + + var 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))) { + _writeInt(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 _writeInt(int value, int byteWidth) { + final newOffset = _newOffset(byteWidth); + _pushInt(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 _pushBuffer(List<int> value) { + _buffer.buffer.asUint8List().setAll(_offset, value); + } +} + +class _StackValue { + Object _value; + int _offset; + ValueType _type; + BitWidth _width; + _StackValue.WithNull() { + _type = ValueType.Null; + _width = BitWidth.width8; + } + _StackValue.WithInt(int value) { + _type = value != null ? ValueType.Int : ValueType.Null; + _width = BitWidthUtil.width(value); + _value = value; + } + _StackValue.WithBool(bool value) { + _type = value != null ? ValueType.Bool : ValueType.Null; + _width = BitWidth.width8; + _value = value; + } + _StackValue.WithDouble(double value) { + _type = value != null ? ValueType.Float : ValueType.Null; + _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; + for(var i = 0; i < 4; i++) { + final width = 1 << i; + final offsetLoc = size + BitWidthUtil.paddingSize(size, width) + index * width; + final offset = offsetLoc - _offset; + final bitWidth = BitWidthUtil.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, Endian.little); + return result.buffer.asUint8List(); + } else { + final result = ByteData(8); + result.setFloat64(0, _value, Endian.little); + return result.buffer.asUint8List(); + } + } else { + switch(width) { + case BitWidth.width8: + final result = ByteData(1); + result.setInt8(0, _value); + return result.buffer.asUint8List(); + case BitWidth.width16: + final result = ByteData(2); + result.setInt16(0, _value, Endian.little); + return result.buffer.asUint8List(); + case BitWidth.width32: + final result = ByteData(4); + result.setInt32(0, _value, Endian.little); + return result.buffer.asUint8List(); + case BitWidth.width64: + final result = ByteData(8); + result.setInt64(0, _value, 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 ? 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; + } +} diff --git a/dart/lib/src/reference.dart b/dart/lib/src/reference.dart new file mode 100644 index 00000000..ad3a2a0a --- /dev/null +++ b/dart/lib/src/reference.dart @@ -0,0 +1,446 @@ +import 'dart:collection'; +import 'dart:convert'; +import 'dart:typed_data'; +import 'types.dart'; + +/// Main class to read a value out of a FlexBuffer. +/// +/// This class let you access values stored in the buffer in a lazy fashion. +class Reference { + final ByteData _buffer; + final int _offset; + final BitWidth _parentWidth; + final String _path; + int _byteWidth; + ValueType _valueType; + int _length; + + Reference._(this._buffer, this._offset, this._parentWidth, int packedType, this._path) { + _byteWidth = 1 << (packedType & 3); + _valueType = ValueTypeUtils.fromInt(packedType >> 2); + } + + /// Use this method to access the root value of a FlexBuffer. + static Reference fromBuffer(ByteBuffer buffer) { + final len = buffer.lengthInBytes; + if (len < 3) { + throw UnsupportedError('Buffer needs to be bigger than 3'); + } + final byteData = ByteData.view(buffer); + final byteWidth = byteData.getUint8(len - 1); + final packedType = byteData.getUint8(len - 2); + final offset = len - byteWidth - 2; + return Reference._(ByteData.view(buffer), offset, BitWidthUtil.fromByteWidth(byteWidth), packedType, "/"); + } + + /// Returns true if the underlying value is null. + bool get isNull => _valueType == ValueType.Null; + /// Returns true if the underlying value can be represented as [num]. + bool get isNum => ValueTypeUtils.isNumber(_valueType) || ValueTypeUtils.isIndirectNumber(_valueType); + /// Returns true if the underlying value was encoded as a float (direct or indirect). + bool get isDouble => _valueType == ValueType.Float || _valueType == ValueType.IndirectFloat; + /// Returns true if the underlying value was encoded as an int or uint (direct or indirect). + bool get isInt => isNum && !isDouble; + /// Returns true if the underlying value was encoded as a string or a key. + bool get isString => _valueType == ValueType.String || _valueType == ValueType.Key; + /// Returns true if the underlying value was encoded as a bool. + bool get isBool => _valueType == ValueType.Bool; + /// Returns true if the underlying value was encoded as a blob. + bool get isBlob => _valueType == ValueType.Blob; + /// Returns true if the underlying value points to a vector. + bool get isVector => ValueTypeUtils.isAVector(_valueType); + /// Returns true if the underlying value points to a map. + bool get isMap => _valueType == ValueType.Map; + + /// If this [isBool], returns the bool value. Otherwise, returns null. + bool get boolValue { + if(_valueType == ValueType.Bool) { + return _readInt(_offset, _parentWidth) != 0; + } + return null; + } + + /// Returns an [int], if the underlying value can be represented as an int. + /// + /// Otherwise returns [null]. + int get intValue { + if (_valueType == ValueType.Int) { + return _readInt(_offset, _parentWidth); + } + if (_valueType == ValueType.UInt) { + return _readUInt(_offset, _parentWidth); + } + if (_valueType == ValueType.IndirectInt) { + return _readInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); + } + if (_valueType == ValueType.IndirectUInt) { + return _readUInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); + } + return null; + } + + /// Returns [double], if the underlying value [isDouble]. + /// + /// Otherwise returns [null]. + double get doubleValue { + if (_valueType == ValueType.Float) { + return _readFloat(_offset, _parentWidth); + } + if (_valueType == ValueType.IndirectFloat) { + return _readFloat(_indirect, BitWidthUtil.fromByteWidth(_byteWidth)); + } + return null; + } + + /// Returns [num], if the underlying value is numeric, be it int uint, or float (direct or indirect). + /// + /// Otherwise returns [null]. + num get numValue => doubleValue ?? intValue; + + /// Returns [String] value or null otherwise. + /// + /// This method performers a utf8 decoding, as FlexBuffers format stores strings in utf8 encoding. + String get stringValue { + if (_valueType == ValueType.String || _valueType == ValueType.Key) { + return utf8.decode(_buffer.buffer.asUint8List(_indirect, length)); + } + return null; + } + + /// Returns [Uint8List] value or null otherwise. + Uint8List get blobValue { + if (_valueType == ValueType.Blob) { + return _buffer.buffer.asUint8List(_indirect, length); + } + return null; + } + + /// Can be used with an [int] or a [String] value for key. + /// If the underlying value in FlexBuffer is a vector, then use [int] for access. + /// If the underlying value in FlexBuffer is a map, then use [String] for access. + /// Returns [Reference] value. Throws an exception when [key] is not applicable. + Reference operator [](Object key) { + if (key is int && ValueTypeUtils.isAVector(_valueType)) { + final index = key; + if(index >= length || index < 0) { + throw ArgumentError('Key: [$key] is not applicable on: $_path of: $_valueType length: $length'); + } + final elementOffset = _indirect + index * _byteWidth; + final reference = Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), 0, "$_path[$index]"); + reference._byteWidth = 1; + if (ValueTypeUtils.isTypedVector(_valueType)) { + reference._valueType = ValueTypeUtils.typedVectorElementType(_valueType); + return reference; + } + if(ValueTypeUtils.isFixedTypedVector(_valueType)) { + reference._valueType = ValueTypeUtils.fixedTypedVectorElementType(_valueType); + return reference; + } + final packedType = _buffer.getUint8(_indirect + length * _byteWidth + index); + return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path[$index]"); + } + if (key is String && _valueType == ValueType.Map) { + final index = _keyIndex(key); + if (index != null) { + return _valueForIndexWithKey(index, key); + } + } + throw ArgumentError('Key: [$key] is not applicable on: $_path of: $_valueType'); + } + + /// Get an iterable if the underlying flexBuffer value is a vector. + /// Otherwise throws an exception. + Iterable<Reference> get vectorIterable { + if(isVector == false) { + throw UnsupportedError('Value is not a vector. It is: $_valueType'); + } + return _VectorIterator(this); + } + + /// Get an iterable for keys if the underlying flexBuffer value is a map. + /// Otherwise throws an exception. + Iterable<String> get mapKeyIterable { + if(isMap == false) { + throw UnsupportedError('Value is not a map. It is: $_valueType'); + } + return _MapKeyIterator(this); + } + + /// Get an iterable for values if the underlying flexBuffer value is a map. + /// Otherwise throws an exception. + Iterable<Reference> get mapValueIterable { + if(isMap == false) { + throw UnsupportedError('Value is not a map. It is: $_valueType'); + } + return _MapValueIterator(this); + } + + /// Returns the length of the the underlying FlexBuffer value. + /// If the underlying value is [null] the length is 0. + /// If the underlying value is a number, or a bool, the length is 1. + /// If the underlying value is a vector, or map, the length reflects number of elements / element pairs. + /// If the values is a string or a blob, the length reflects a number of bytes the value occupies (strings are encoded in utf8 format). + int get length { + if (_length != null) { + return _length; + } + // needs to be checked before more generic isAVector + if(ValueTypeUtils.isFixedTypedVector(_valueType)) { + _length = ValueTypeUtils.fixedTypedVectorElementSize(_valueType); + } else if(_valueType == ValueType.Blob || ValueTypeUtils.isAVector(_valueType) || _valueType == ValueType.Map){ + _length = _readInt(_indirect - _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); + } else if (_valueType == ValueType.Null) { + _length = 0; + } else if (_valueType == ValueType.String) { + final indirect = _indirect; + var size_byte_width = _byteWidth; + var size = _readInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width)); + while (_buffer.getInt8(indirect + size) != 0) { + size_byte_width <<= 1; + size = _readInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width)); + } + _length = size; + } else if (_valueType == ValueType.Key) { + final indirect = _indirect; + var size = 1; + while (_buffer.getInt8(indirect + size) != 0) { + size += 1; + } + _length = size; + } else { + _length = 1; + } + return _length; + } + + + /// Returns a minified JSON representation of the underlying FlexBuffer value. + /// + /// This method involves materializing the entire object tree, which may be + /// expensive. It is more efficient to work with [Reference] and access only the needed data. + /// Blob values are represented as base64 encoded string. + String get json { + if(_valueType == ValueType.Bool) { + return boolValue ? 'true' : 'false'; + } + if (_valueType == ValueType.Null) { + return 'null'; + } + if(ValueTypeUtils.isNumber(_valueType)) { + return jsonEncode(numValue); + } + if (_valueType == ValueType.String) { + return jsonEncode(stringValue); + } + if (_valueType == ValueType.Blob) { + return jsonEncode(base64Encode(blobValue)); + } + if (ValueTypeUtils.isAVector(_valueType)) { + final result = StringBuffer(); + result.write('['); + for (var i = 0; i < length; i++) { + result.write(this[i].json); + if (i < length - 1) { + result.write(','); + } + } + result.write(']'); + return result.toString(); + } + if (_valueType == ValueType.Map) { + final result = StringBuffer(); + result.write('{'); + for (var i = 0; i < length; i++) { + result.write(jsonEncode(_keyForIndex(i))); + result.write(':'); + result.write(_valueForIndex(i).json); + if (i < length - 1) { + result.write(','); + } + } + result.write('}'); + return result.toString(); + } + throw UnsupportedError('Type: $_valueType is not supported for JSON conversion'); + } + + /// Computes the indirect offset of the value. + /// + /// To optimize for the more common case of being called only once, this + /// value is not cached. Callers that need to use it more than once should + /// cache the return value in a local variable. + int get _indirect { + final step = _readInt(_offset, _parentWidth); + return _offset - step; + } + + int _readInt(int offset, BitWidth width) { + _validateOffset(offset, width); + if (width == BitWidth.width8) { + return _buffer.getInt8(offset); + } + if (width == BitWidth.width16) { + return _buffer.getInt16(offset, Endian.little); + } + if (width == BitWidth.width32) { + return _buffer.getInt32(offset, Endian.little); + } + return _buffer.getInt64(offset, Endian.little); + } + + int _readUInt(int offset, BitWidth width) { + _validateOffset(offset, width); + if (width == BitWidth.width8) { + return _buffer.getUint8(offset); + } + if (width == BitWidth.width16) { + return _buffer.getUint16(offset, Endian.little); + } + if (width == BitWidth.width32) { + return _buffer.getUint32(offset, Endian.little); + } + return _buffer.getUint64(offset, Endian.little); + } + + double _readFloat(int offset, BitWidth width) { + _validateOffset(offset, width); + if (width.index < BitWidth.width32.index) { + throw StateError('Bad width: $width'); + } + + if (width == BitWidth.width32) { + return _buffer.getFloat32(offset, Endian.little); + } + + return _buffer.getFloat64(offset, Endian.little); + } + + void _validateOffset(int offset, BitWidth width) { + if (_offset < 0 || _buffer.lengthInBytes <= offset + width.index || offset & (BitWidthUtil.toByteWidth(width) - 1) != 0) { + throw StateError('Bad offset: $offset, width: $width'); + } + } + + int _keyIndex(String key) { + final input = utf8.encode(key); + final keysVectorOffset = _indirect - _byteWidth * 3; + final indirectOffset = keysVectorOffset - _readInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth)); + final byteWidth = _readInt(keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); + var low = 0; + var high = length - 1; + while (low <= high) { + final mid = (high + low) >> 1; + final dif = _diffKeys(input, mid, indirectOffset, byteWidth); + if (dif == 0) return mid; + if (dif < 0) { + high = mid - 1; + } else { + low = mid + 1; + } + } + return null; + } + + int _diffKeys(List<int> input, int index, int indirect_offset, int byteWidth) { + final keyOffset = indirect_offset + index * byteWidth; + final keyIndirectOffset = keyOffset - _readInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth)); + for (var i = 0; i < input.length; i++) { + final dif = input[i] - _buffer.getUint8(keyIndirectOffset + i); + if (dif != 0) { + return dif; + } + } + return (_buffer.getUint8(keyIndirectOffset + input.length) == 0) ? 0 : -1; + } + + Reference _valueForIndexWithKey(int index, String key) { + final indirect = _indirect; + final elementOffset = indirect + index * _byteWidth; + final packedType = _buffer.getUint8(indirect + length * _byteWidth + index); + return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/$key"); + } + + Reference _valueForIndex(int index) { + final indirect = _indirect; + final elementOffset = indirect + index * _byteWidth; + final packedType = _buffer.getUint8(indirect + length * _byteWidth + index); + return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/[$index]"); + } + + String _keyForIndex(int index) { + final keysVectorOffset = _indirect - _byteWidth * 3; + final indirectOffset = keysVectorOffset - _readInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth)); + final byteWidth = _readInt(keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth)); + final keyOffset = indirectOffset + index * byteWidth; + final keyIndirectOffset = keyOffset - _readInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth)); + var length = 0; + while (_buffer.getUint8(keyIndirectOffset + length) != 0) { + length += 1; + } + return utf8.decode(_buffer.buffer.asUint8List(keyIndirectOffset, length)); + } + +} + +class _VectorIterator with IterableMixin<Reference> implements Iterator<Reference> { + final Reference _vector; + int index; + + _VectorIterator(this._vector) { + index = -1; + } + + @override + Reference get current => _vector[index]; + + @override + bool moveNext() { + index++; + return index < _vector.length; + } + + @override + Iterator<Reference> get iterator => this; +} + +class _MapKeyIterator with IterableMixin<String> implements Iterator<String> { + final Reference _map; + int index; + + _MapKeyIterator(this._map) { + index = -1; + } + + @override + String get current => _map._keyForIndex(index); + + @override + bool moveNext() { + index++; + return index < _map.length; + } + + @override + Iterator<String> get iterator => this; +} + +class _MapValueIterator with IterableMixin<Reference> implements Iterator<Reference> { + final Reference _map; + int index; + + _MapValueIterator(this._map) { + index = -1; + } + + @override + Reference get current => _map._valueForIndex(index); + + @override + bool moveNext() { + index++; + return index < _map.length; + } + + @override + Iterator<Reference> get iterator => this; +} diff --git a/dart/lib/src/types.dart b/dart/lib/src/types.dart new file mode 100644 index 00000000..09e55206 --- /dev/null +++ b/dart/lib/src/types.dart @@ -0,0 +1,156 @@ +import 'dart:typed_data'; + +/// Represents the number of bits a value occupies. +enum BitWidth { + width8, + width16, + width32, + width64 +} + +class BitWidthUtil { + static int toByteWidth(BitWidth self) { + return 1 << self.index; + } + static BitWidth width(num value) { + if (value.toInt() == value) { + var v = value.toInt().abs(); + if (v >> 7 == 0) return BitWidth.width8; + if (v >> 15 == 0) return BitWidth.width16; + if (v >> 31 == 0) return BitWidth.width32; + return BitWidth.width64; + } + return value == _toF32(value) ? BitWidth.width32 : BitWidth.width64; + } + static BitWidth fromByteWidth(int value) { + if (value == 1) { + return BitWidth.width8; + } + if (value == 2) { + return BitWidth.width16; + } + if (value == 4) { + return BitWidth.width32; + } + if (value == 8) { + return BitWidth.width64; + } + throw Exception('Unexpected value ${value}'); + } + static int paddingSize(int bufSize, int scalarSize) { + return (~bufSize + 1) & (scalarSize - 1); + } + static double _toF32(double value) { + var bdata = ByteData(4); + bdata.setFloat32(0, value); + return bdata.getFloat32(0); + } + + static BitWidth max(BitWidth self, BitWidth other) { + if (self.index < other.index) { + return other; + } + return self; + } +} + +/// Represents all internal FlexBuffer types. +enum ValueType { + Null, Int, UInt, Float, + Key, String, IndirectInt, IndirectUInt, IndirectFloat, + Map, Vector, VectorInt, VectorUInt, VectorFloat, VectorKey, + @Deprecated('VectorString is deprecated due to a flaw in the binary format (https://github.com/google/flatbuffers/issues/5627)') + VectorString, + VectorInt2, VectorUInt2, VectorFloat2, + VectorInt3, VectorUInt3, VectorFloat3, + VectorInt4, VectorUInt4, VectorFloat4, + Blob, Bool, VectorBool +} + +class ValueTypeUtils { + static int toInt(ValueType self) { + if (self == ValueType.VectorBool) return 36; + return self.index; + } + + static ValueType fromInt(int value) { + if (value == 36) return ValueType.VectorBool; + return ValueType.values[value]; + } + + static bool isInline(ValueType self) { + return self == ValueType.Bool + || toInt(self) <= toInt(ValueType.Float); + } + + static bool isNumber(ValueType self) { + return toInt(self) >= toInt(ValueType.Int) + && toInt(self) <= toInt(ValueType.Float); + } + + static bool isIndirectNumber(ValueType self) { + return toInt(self) >= toInt(ValueType.IndirectInt) + && toInt(self) <= toInt(ValueType.IndirectFloat); + } + + static bool isTypedVectorElement(ValueType self) { + return self == ValueType.Bool || + ( + toInt(self) >= toInt(ValueType.Int) + && toInt(self) <= toInt(ValueType.String) + ); + } + + static bool isTypedVector(ValueType self) { + return self == ValueType.VectorBool || + ( + toInt(self) >= toInt(ValueType.VectorInt) + && toInt(self) <= toInt(ValueType.VectorString) + ); + } + + static bool isFixedTypedVector(ValueType self) { + return ( + toInt(self) >= toInt(ValueType.VectorInt2) + && toInt(self) <= toInt(ValueType.VectorFloat4) + ); + } + + static bool isAVector(ValueType self) { + return ( + isTypedVector(self) || isFixedTypedVector(self) || self == ValueType.Vector + ); + } + + static ValueType toTypedVector(ValueType self, int length) { + if (length == 0) { + return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt)); + } + if (length == 2) { + return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt2)); + } + if (length == 3) { + return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt3)); + } + if (length == 4) { + return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt4)); + } + throw Exception('unexpected length ' + length.toString()); + } + + static ValueType typedVectorElementType(ValueType self) { + return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.VectorInt) + toInt(ValueType.Int)); + } + + static ValueType fixedTypedVectorElementType(ValueType self) { + return ValueTypeUtils.fromInt((toInt(self) - toInt(ValueType.VectorInt2)) % 3 + toInt(ValueType.Int)); + } + + static int fixedTypedVectorElementSize(ValueType self) { + return (toInt(self) - toInt(ValueType.VectorInt2)) ~/ 3 + 2; + } + + static int packedType(ValueType self, BitWidth bitWidth) { + return bitWidth.index | (toInt(self) << 2); + } +} diff --git a/dart/test/flex_builder_test.dart b/dart/test/flex_builder_test.dart new file mode 100644 index 00000000..5a1c1a8f --- /dev/null +++ b/dart/test/flex_builder_test.dart @@ -0,0 +1,315 @@ +import 'dart:typed_data'; + +import 'package:flat_buffers/flex_buffers.dart' show Builder; +import 'package:test/test.dart'; + +void main() { + test('build with single value', () { + { + var flx = Builder(); + flx.addNull(); + expect(flx.finish(), [0, 0, 1]); + } + { + var flx = Builder(); + flx.addBool(true); + expect(flx.finish(), [1, 104, 1]); + } + { + var flx = Builder(); + flx.addBool(false); + expect(flx.finish(), [0, 104, 1]); + } + { + var flx = Builder(); + flx.addInt(1); + expect(flx.finish(), [1, 4, 1]); + } + { + var flx = Builder(); + flx.addInt(230); + expect(flx.finish(), [230, 0, 5, 2]); + } + { + var flx = Builder(); + flx.addInt(1025); + expect(flx.finish(), [1, 4, 5, 2]); + } + { + var flx = Builder(); + flx.addInt(-1025); + expect(flx.finish(), [255, 251, 5, 2]); + } + { + var flx = Builder(); + flx.addDouble(0.1); + expect(flx.finish(), [154, 153, 153, 153, 153, 153, 185, 63, 15, 8]); + } + { + var flx = Builder(); + flx.addDouble(0.5); + expect(flx.finish(), [0, 0, 0, 63, 14, 4]); + } + { + var flx = Builder(); + flx.addString('Maxim'); + expect(flx.finish(), [5, 77, 97, 120, 105, 109, 0, 6, 20, 1]); + } + { + var flx = Builder(); + flx.addString('hello 😱'); + expect(flx.finish(), [10, 104, 101, 108, 108, 111, 32, 240, 159, 152, 177, 0, 11, 20, 1]); + } + }); + + test('build vector', (){ + { + var flx = Builder() + ..startVector() + ..addInt(1) + ..addInt(2) + ..end() + ; + expect(flx.finish(), [1, 2, 2, 64, 1]); + } + { + var flx = Builder() + ..startVector() + ..addInt(-1) + ..addInt(256) + ..end() + ; + expect(flx.finish(), [255, 255, 0, 1, 4, 65, 1]); + } + { + var flx = Builder() + ..startVector() + ..addInt(-45) + ..addInt(256000) + ..end() + ; + expect(flx.finish(), [211, 255, 255, 255, 0, 232, 3, 0, 8, 66, 1]); + } + { + var flx = Builder() + ..startVector() + ..addDouble(1.1) + ..addDouble(-256) + ..end() + ; + expect(flx.finish(), [154, 153, 153, 153, 153, 153, 241, 63, 0, 0, 0, 0, 0, 0, 112, 192, 16, 75, 1]); + } + { + var flx = Builder() + ..startVector() + ..addInt(1) + ..addInt(2) + ..addInt(4) + ..end() + ; + expect(flx.finish(), [1, 2, 4, 3, 76, 1]); + } + { + var flx = Builder() + ..startVector() + ..addInt(-1) + ..addInt(256) + ..addInt(4) + ..end() + ; + expect(flx.finish(), [255, 255, 0, 1, 4, 0, 6, 77, 1]); + } + { + var flx = Builder() + ..startVector() + ..startVector() + ..addInt(61) + ..end() + ..addInt(64) + ..end() + ; + expect(flx.finish(), [1, 61, 2, 2, 64, 44, 4, 4, 40, 1]); + } + { + var flx = Builder() + ..startVector() + ..addString('foo') + ..addString('bar') + ..addString('baz') + ..end() + ; + expect(flx.finish(), [3, 102, 111, 111, 0, 3, 98, 97, 114, 0, 3, 98, 97, 122, 0, 3, 15, 11, 7, 3, 60, 1]); + } + { + var flx = Builder() + ..startVector() + ..addString('foo') + ..addString('bar') + ..addString('baz') + ..addString('foo') + ..addString('bar') + ..addString('baz') + ..end() + ; + expect(flx.finish(), [3, 102, 111, 111, 0, 3, 98, 97, 114, 0, 3, 98, 97, 122, 0, 6, 15, 11, 7, 18, 14, 10, 6, 60, 1]); + } + { + var flx = Builder() + ..startVector() + ..addBool(true) + ..addBool(false) + ..addBool(true) + ..end() + ; + expect(flx.finish(), [3, 1, 0, 1, 3, 144, 1]); + } + { + var flx = Builder() + ..startVector() + ..addString('foo') + ..addInt(1) + ..addInt(-5) + ..addDouble(1.3) + ..addBool(true) + ..end() + ; + expect(flx.finish(), [ + 3, 102, 111, 111, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, + 251, 255, 255, 255, 255, 255, 255, 255, + 205, 204, 204, 204, 204, 204, 244, 63, + 1, 0, 0, 0, 0, 0, 0, 0, + 20, 4, 4, 15, 104, 45, 43, 1]); + } + }); + + test('build map', () + { + { + var flx = Builder() + ..startMap() + ..addKey('a') + ..addInt(12) + ..end() + ; + expect(flx.finish(), [97, 0, 1, 3, 1, 1, 1, 12, 4, 2, 36, 1]); + } + { + var flx = Builder() + ..startMap() + ..addKey('a') + ..addInt(12) + ..addKey('') + ..addInt(45) + ..end() + ; + expect(flx.finish(), [97, 0, 0, 2, 2, 5, 2, 1, 2, 45, 12, 4, 4, 4, 36, 1]); + } + { + var flx = Builder() + ..startVector() + ..startMap() + ..addKey('something') + ..addInt(12) + ..end() + ..startMap() + ..addKey('something') + ..addInt(45) + ..end() + ..end() + ; + expect(flx.finish(), [115, 111, 109, 101, 116, 104, 105, 110, 103, 0, + 1, 11, 1, 1, 1, 12, 4, 6, 1, 1, 45, 4, 2, 8, 4, 36, 36, 4, 40, 1]); + } + }); + + test('build blob', () + { + { + var flx = Builder() + ..addBlob(Uint8List.fromList([1, 2, 3]).buffer) + ; + expect(flx.finish(), [3, 1, 2, 3, 3, 100, 1]); + } + }); + + test('build from object', (){ + expect(Builder.buildFromObject(Uint8List.fromList([1, 2, 3]).buffer).asUint8List(), [3, 1, 2, 3, 3, 100, 1]); + expect(Builder.buildFromObject(null).asUint8List(), [0, 0, 1]); + expect(Builder.buildFromObject(true).asUint8List(), [1, 104, 1]); + expect(Builder.buildFromObject(false).asUint8List(), [0, 104, 1]); + expect(Builder.buildFromObject(25).asUint8List(), [25, 4, 1]); + expect(Builder.buildFromObject(-250).asUint8List(), [6, 255, 5, 2]); + expect(Builder.buildFromObject(-2.50).asUint8List(), [0, 0, 32, 192, 14, 4]); + expect(Builder.buildFromObject('Maxim').asUint8List(), [5, 77, 97, 120, 105, 109, 0, 6, 20, 1]); + expect(Builder.buildFromObject([1, 3.3, 'max', true, null, false]).asUint8List(), [ + 3, 109, 97, 120, 0, 0, 0, 0, + 6, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, + 102, 102, 102, 102, 102, 102, 10, 64, + 31, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 4, 15, 20, 104, 0, 104, 54, 43, 1 + ]); + expect(Builder.buildFromObject([{'something':12}, {'something': 45}]).asUint8List(), [ + 115, 111, 109, 101, 116, 104, 105, 110, 103, 0, + 1, 11, 1, 1, 1, 12, 4, 6, 1, 1, 45, 4, 2, 8, 4, 36, 36, 4, 40, 1 + ]); + }); + + test('add double indirectly', (){ + var flx = Builder() + ..addDoubleIndirectly(0.1) + ; + expect(flx.finish(), [154, 153, 153, 153, 153, 153, 185, 63, 8, 35, 1]); + }); + + test('add double indirectly to vector with cache', (){ + var flx = Builder() + ..startVector() + ..addDoubleIndirectly(0.1, cache: true) + ..addDoubleIndirectly(0.1, cache: true) + ..addDoubleIndirectly(0.1, cache: true) + ..addDoubleIndirectly(0.1, cache: true) + ..end() + ; + expect(flx.finish(), [154, 153, 153, 153, 153, 153, 185, 63, + 4, 9, 10, 11, 12, 35, 35, 35, 35, 8, 40, 1]); + }); + + test('add int indirectly', (){ + var flx = Builder() + ..addIntIndirectly(2345234523452345) + ; + expect(flx.finish(), [185, 115, 175, 118, 250, 84, 8, 0, 8, 27, 1]); + }); + + test('add int indirectly to vector with cache', (){ + var flx = Builder() + ..startVector() + ..addIntIndirectly(2345234523452345, cache: true) + ..addIntIndirectly(2345234523452345, cache: true) + ..addIntIndirectly(2345234523452345, cache: true) + ..addIntIndirectly(2345234523452345, cache: true) + ..end() + ; + expect(flx.finish(), [185, 115, 175, 118, 250, 84, 8, 0, + 4, 9, 10, 11, 12, 27, 27, 27, 27, 8, 40, 1]); + }); + + test('snapshot', (){ + var flx = Builder(); + flx.startVector(); + flx.addInt(12); + expect(flx.snapshot().asUint8List(), [1, 12, 1, 44, 1]); + flx.addInt(24); + expect(flx.snapshot().asUint8List(), [12, 24, 2, 64, 1]); + flx.addInt(45); + expect(flx.snapshot().asUint8List(), [12, 24, 45, 3, 76, 1]); + }); +} + diff --git a/dart/test/flex_reader_test.dart b/dart/test/flex_reader_test.dart new file mode 100644 index 00000000..47e7a3f9 --- /dev/null +++ b/dart/test/flex_reader_test.dart @@ -0,0 +1,316 @@ +import 'dart:typed_data'; + +import 'package:flat_buffers/flex_buffers.dart' show Reference; +import 'package:test/test.dart'; + +void main() { + test('is null', () { + expect(Reference.fromBuffer(b([0, 0, 1])).isNull, isTrue); + }); + + test('bool value', () { + expect(Reference.fromBuffer(b([1, 104, 1])).boolValue, isTrue); + expect(Reference.fromBuffer(b([0, 104, 1])).boolValue, isFalse); + }); + test('int value', () { + expect(Reference.fromBuffer(b([25, 4, 1])).intValue, 25); + expect(Reference.fromBuffer(b([231, 4, 1])).intValue, -25); + expect(Reference.fromBuffer(b([230, 8, 1])).intValue, 230); + expect(Reference.fromBuffer(b([230, 0, 5, 2])).intValue, 230); + expect(Reference.fromBuffer(b([1, 4, 5, 2])).intValue, 1025); + expect(Reference.fromBuffer(b([255, 251, 5, 2])).intValue, -1025); + expect(Reference.fromBuffer(b([1, 4, 9, 2])).intValue, 1025); + expect(Reference.fromBuffer(b([255, 255, 255, 127, 6, 4])).intValue, + 2147483647); + expect( + Reference.fromBuffer(b([0, 0, 0, 128, 6, 4])).intValue, -2147483648); + expect( + Reference.fromBuffer(b([255, 255, 255, 255, 0, 0, 0, 0, 7, 8])) + .intValue, + 4294967295); + expect( + Reference.fromBuffer(b([255, 255, 255, 255, 255, 255, 255, 127, 7, 8])) + .intValue, + 9223372036854775807); + expect(Reference.fromBuffer(b([0, 0, 0, 0, 0, 0, 0, 128, 7, 8])).intValue, + -9223372036854775808); + // Dart does not really support UInt64 +// expect(FlxValue.fromBuffer(b([255, 255, 255, 255, 255, 255, 255, 255, 11, 8])).intValue, 18446744073709551615); + }); + test('double value', () { + expect(Reference.fromBuffer(b([0, 0, 144, 64, 14, 4])).doubleValue, 4.5); + expect(Reference.fromBuffer(b([205, 204, 204, 61, 14, 4])).doubleValue, + closeTo(.1, .001)); + expect( + Reference.fromBuffer(b([154, 153, 153, 153, 153, 153, 185, 63, 15, 8])) + .doubleValue, + .1); + }); + test('num value', () { + expect(Reference.fromBuffer(b([0, 0, 144, 64, 14, 4])).numValue, 4.5); + expect(Reference.fromBuffer(b([205, 204, 204, 61, 14, 4])).numValue, + closeTo(.1, .001)); + expect( + Reference.fromBuffer(b([154, 153, 153, 153, 153, 153, 185, 63, 15, 8])) + .numValue, + .1); + expect(Reference.fromBuffer(b([255, 251, 5, 2])).numValue, -1025); + }); + test('string value', () { + expect( + Reference.fromBuffer(b([5, 77, 97, 120, 105, 109, 0, 6, 20, 1])) + .stringValue, + 'Maxim'); + expect( + Reference.fromBuffer(b([ + 10, 104, 101, 108, 108, 111, 32, 240, 159, 152, 177, 0, 11, 20, 1 + ])).stringValue, + 'hello 😱'); + }); + test('blob value', () { + expect( + Reference.fromBuffer(b([3, 1, 2, 3, 3, 100, 1])).blobValue, [1, 2, 3]); + }); + test('bool vector', () { + var flx = Reference.fromBuffer(b([3, 1, 0, 1, 3, 144, 1])); + expect(flx[0].boolValue, true); + expect(flx[1].boolValue, false); + expect(flx[2].boolValue, true); + }); + test('number vector', () { + testNumbers([3, 1, 2, 3, 3, 44, 1], [1, 2, 3]); + testNumbers([3, 255, 2, 3, 3, 44, 1], [-1, 2, 3]); + testNumbers([3, 0, 1, 0, 43, 2, 3, 0, 6, 45, 1], [1, 555, 3]); + testNumbers( + [3, 0, 0, 0, 1, 0, 0, 0, 204, 216, 0, 0, 3, 0, 0, 0, 12, 46, 1], + [1, 55500, 3]); + testNumbers([ + 3, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, + 172, 128, 94, 239, 12, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, + 24, 47, 1 + ], [1, 55555555500, 3 + ]); + testNumbers( + [3, 0, 0, 0, 0, 0, 192, 63, 0, 0, 32, 64, 0, 0, 96, 64, 12, 54, 1], + [1.5, 2.5, 3.5]); + testNumbers([ + 3, 0, 0, 0, 0, 0, 0, 0, + 154, 153, 153, 153, 153, 153, 241, 63, + 154, 153, 153, 153, 153, 153, 1, 64, + 102, 102, 102, 102, 102, 102, 10, 64, + 24, 55, 1 + ], [1.1, 2.2, 3.3 + ]); + }); + test('number vector, fixed type', () { + testNumbers([1, 2, 2, 64, 1], [1, 2]); + testNumbers([255, 255, 0, 1, 4, 65, 1], [-1, 256]); + testNumbers([211, 255, 255, 255, 0, 232, 3, 0, 8, 66, 1], [-45, 256000]); + testNumbers([ + 211, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 127, + 16, 67, 1 + ], [ + -45, 9223372036854775807 + ]); + + testNumbers([1, 2, 2, 68, 1], [1, 2]); + testNumbers([1, 0, 0, 1, 4, 69, 1], [1, 256]); + testNumbers([45, 0, 0, 0, 0, 232, 3, 0, 8, 70, 1], [45, 256000]); + + testNumbers([205, 204, 140, 63, 0, 0, 0, 192, 8, 74, 1], [1.1, -2]); + testNumbers([ + 154, 153, 153, 153, 153, 153, 241, 63, + 0, 0, 0, 0, 0, 0, 112, 192, + 16, 75, 1 + ], [ + 1.1, -256 + ]); + + testNumbers([211, 255, 255, 255, 0, 232, 3, 0, 4, 0, 0, 0, 12, 78, 1], + [-45, 256000, 4]); + + testNumbers([ + 211, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 127, + 4, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, + 32, 91, 1 + ], [ + -45, 9223372036854775807, 4, 9 + ]); + + testNumbers([ + 45, 0, 0, 0, 0, 0, 0, 0, + 255, 255, 255, 255, 255, 255, 255, 127, + 4, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, + 32, 95, 1 + ], [ + 45, 9223372036854775807, 4, 9 + ]); + + testNumbers([ + 154, 153, 153, 153, 153, 153, 241, 63, + 0, 0, 0, 0, 0, 0, 112, 64, + 0, 0, 0, 0, 0, 0, 16, 64, + 24, 87, 1 + ], [ + 1.1, 256, 4 + ]); + + testNumbers([ + 154, 153, 153, 153, 153, 153, 241, 63, + 0, 0, 0, 0, 0, 0, 112, 64, + 0, 0, 0, 0, 0, 0, 16, 64, + 0, 0, 0, 0, 0, 0, 34, 64, + 32, 99, 1 + ], [ + 1.1, 256, 4, 9 + ]); + }); + test('string vector', () { + testStrings([ + 3, 102, 111, 111, 0, + 3, 98, 97, 114, 0, + 3, 98, 97, 122, 0, + 3, 15, 11, 7, + 3, 60, 1 + ], [ + 'foo', 'bar', 'baz' + ]); + testStrings([ + 3, 102, 111, 111, 0, + 3, 98, 97, 114, 0, + 3, 98, 97, 122, 0, + 6, 15, 11, 7, 18, 14, 10, + 6, 60, 1 + ], [ + 'foo', 'bar', 'baz', 'foo', 'bar', 'baz' + ]); + }); + test('mixed vector', () { + var flx = Reference.fromBuffer(b([ + 3, 102, 111, 111, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, + 251, 255, 255, 255, 255, 255, 255, 255, + 205, 204, 204, 204, 204, 204, 244, 63, + 1, 0, 0, 0, 0, 0, 0, 0, + 20, 4, 4, 15, 104, 45, 43, 1 + ])); + expect(flx.length, 5); + expect(flx[0].stringValue, 'foo'); + expect(flx[1].numValue, 1); + expect(flx[2].numValue, -5); + expect(flx[3].numValue, 1.3); + expect(flx[4].boolValue, true); + }); + + test('single value map', () { + var flx = Reference.fromBuffer(b([97, 0, 1, 3, 1, 1, 1, 12, 4, 2, 36, 1])); + expect(flx.length, 1); + expect(flx['a'].numValue, 12); + }); + test('two value map', () { + var flx = Reference.fromBuffer(b([0, 97, 0, 2, 4, 4, 2, 1, 2, 45, 12, 4, 4, 4, 36, 1])); + expect(flx.length, 2); + expect(flx['a'].numValue, 12); + expect(flx[''].numValue, 45); + }); + test('complex map', () { + var flx = complexMap(); + expect(flx.length, 5); + expect(flx['age'].numValue, 35); + expect(flx['weight'].numValue, 72.5); + expect(flx['name'].stringValue, 'Maxim'); + + expect(flx['flags'].length, 4); + expect(flx['flags'][0].boolValue, true); + expect(flx['flags'][1].boolValue, false); + expect(flx['flags'][2].boolValue, true); + expect(flx['flags'][3].boolValue, true); + + expect(flx['address'].length, 3); + expect(flx['address']['city'].stringValue, 'Bla'); + expect(flx['address']['zip'].stringValue, '12345'); + expect(flx['address']['countryCode'].stringValue, 'XX'); + + expect(() => flx['address']['country'].stringValue, + throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [country] is not applicable on: //address of: ValueType.Map'))); + expect(() => flx['address']['countryCode'][0], + throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [0] is not applicable on: //address/countryCode of: ValueType.String'))); + expect(() => flx[1], + throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [1] is not applicable on: / of: ValueType.Map'))); + expect(() => flx['flags'][4], + throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [4] is not applicable on: //flags of: ValueType.VectorBool length: 4'))); + expect(() => flx['flags'][-1], + throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [-1] is not applicable on: //flags of: ValueType.VectorBool length: 4'))); + }); + test('complex map to json', () { + var flx = complexMap(); + expect(flx.json, '{"address":{"city":"Bla","countryCode":"XX","zip":"12345"},"age":35,"flags":[true,false,true,true],"name":"Maxim","weight":72.5}'); + }); + + test('complex map iterators', () { + var flx = complexMap(); + expect(flx.mapKeyIterable.map((e) => e).toList(), ['address', 'age', 'flags', 'name', 'weight']); + expect(flx.mapValueIterable.map((e) => e.json).toList(), [flx['address'].json, flx['age'].json, flx['flags'].json, flx['name'].json, flx['weight'].json]); + expect(flx['flags'].vectorIterable.map((e) => e.boolValue).toList(), [true, false, true, true]); + }); +} + +ByteBuffer b(List<int> values) { + var data = Uint8List.fromList(values); + return data.buffer; +} + +void testNumbers(List<int> buffer, List<num> numbers) { + var flx = Reference.fromBuffer(b(buffer)); + expect(flx.length, numbers.length); + for (var i = 0; i < flx.length; i++) { + expect(flx[i].numValue, closeTo(numbers[i], 0.001)); + } +} + +void testStrings(List<int> buffer, List<String> numbers) { + var flx = Reference.fromBuffer(b(buffer)); + expect(flx.length, numbers.length); + for (var i = 0; i < flx.length; i++) { + expect(flx[i].stringValue, numbers[i]); + } +} + +Reference complexMap(){ +// { +// "age": 35, +// "flags": [True, False, True, True], +// "weight": 72.5, +// "name": "Maxim", +// "address": { +// "city": "Bla", +// "zip": "12345", +// "countryCode": "XX", +// } +// } + return Reference.fromBuffer(b([ + 97, 100, 100, 114, 101, 115, 115, 0, + 99, 105, 116, 121, 0, 3, 66, 108, 97, 0, + 99, 111, 117, 110, 116, 114, 121, 67, 111, 100, 101, 0, + 2, 88, 88, 0, + 122, 105, 112, 0, + 5, 49, 50, 51, 52, 53, 0, + 3, 38, 29, 14, 3, 1, 3, 38, 22, 15, 20, 20, 20, + 97, 103, 101, 0, + 102, 108, 97, 103, 115, 0, + 4, 1, 0, 1, 1, + 110, 97, 109, 101, 0, + 5, 77, 97, 120, 105, 109, 0, + 119, 101, 105, 103, 104, 116, 0, + 5, 93, 36, 33, 23, 12, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 60, 0, 0, 0, 35, 0, 0, 0, 51, 0, 0, 0, 45, + 0, 0, 0, 0, 0, 145, 66, 36, 4, 144, 20, 14, 25, 38, 1 + ])); +} diff --git a/dart/test/flex_types_test.dart b/dart/test/flex_types_test.dart new file mode 100644 index 00000000..16235f62 --- /dev/null +++ b/dart/test/flex_types_test.dart @@ -0,0 +1,137 @@ +import 'package:flat_buffers/src/types.dart'; +import 'package:test/test.dart'; + +void main() { + test('is inline', () { + expect(ValueTypeUtils.isInline(ValueType.Bool), isTrue); + expect(ValueTypeUtils.isInline(ValueType.Int), isTrue); + expect(ValueTypeUtils.isInline(ValueType.UInt), isTrue); + expect(ValueTypeUtils.isInline(ValueType.Float), isTrue); + expect(ValueTypeUtils.isInline(ValueType.Null), isTrue); + expect(ValueTypeUtils.isInline(ValueType.String), isFalse); + }); + test('is type vector element', () { + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Bool), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Int), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.UInt), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Float), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Key), isTrue); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.String), isTrue); + + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Null), isFalse); + expect(ValueTypeUtils.isTypedVectorElement(ValueType.Blob), isFalse); + }); + test('is typed vector', () { + expect(ValueTypeUtils.isTypedVector(ValueType.VectorInt), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorUInt), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorFloat), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorBool), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorKey), isTrue); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorString), isTrue); + + expect(ValueTypeUtils.isTypedVector(ValueType.Vector), isFalse); + expect(ValueTypeUtils.isTypedVector(ValueType.Map), isFalse); + expect(ValueTypeUtils.isTypedVector(ValueType.Bool), isFalse); + expect(ValueTypeUtils.isTypedVector(ValueType.VectorInt2), isFalse); + }); + test('is fixed typed vector', () { + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt2), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt3), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt4), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt2), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt3), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt4), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat2), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat3), isTrue); + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat4), isTrue); + + expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt), isFalse); + }); + test('to typed vector', () { + expect(ValueTypeUtils.toTypedVector(ValueType.Int,0), equals(ValueType.VectorInt)); + expect(ValueTypeUtils.toTypedVector(ValueType.UInt,0), equals(ValueType.VectorUInt)); + expect(ValueTypeUtils.toTypedVector(ValueType.Bool,0), equals(ValueType.VectorBool)); + expect(ValueTypeUtils.toTypedVector(ValueType.Float,0), equals(ValueType.VectorFloat)); + expect(ValueTypeUtils.toTypedVector(ValueType.Key,0), equals(ValueType.VectorKey)); + expect(ValueTypeUtils.toTypedVector(ValueType.String,0), equals(ValueType.VectorString)); + + expect(ValueTypeUtils.toTypedVector(ValueType.Int,2), equals(ValueType.VectorInt2)); + expect(ValueTypeUtils.toTypedVector(ValueType.UInt,2), equals(ValueType.VectorUInt2)); + expect(ValueTypeUtils.toTypedVector(ValueType.Float,2), equals(ValueType.VectorFloat2)); + + expect(ValueTypeUtils.toTypedVector(ValueType.Int,3), equals(ValueType.VectorInt3)); + expect(ValueTypeUtils.toTypedVector(ValueType.UInt,3), equals(ValueType.VectorUInt3)); + expect(ValueTypeUtils.toTypedVector(ValueType.Float,3), equals(ValueType.VectorFloat3)); + + expect(ValueTypeUtils.toTypedVector(ValueType.Int,4), equals(ValueType.VectorInt4)); + expect(ValueTypeUtils.toTypedVector(ValueType.UInt,4), equals(ValueType.VectorUInt4)); + expect(ValueTypeUtils.toTypedVector(ValueType.Float,4), equals(ValueType.VectorFloat4)); + }); + test('typed vector element type', () { + expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorInt), equals(ValueType.Int)); + expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorUInt), equals(ValueType.UInt)); + expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorFloat), equals(ValueType.Float)); + expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorString), equals(ValueType.String)); + expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorKey), equals(ValueType.Key)); + expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorBool), equals(ValueType.Bool)); + }); + test('fixed typed vector element type', () { + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt2), equals(ValueType.Int)); + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt3), equals(ValueType.Int)); + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt4), equals(ValueType.Int)); + + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt2), equals(ValueType.UInt)); + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt3), equals(ValueType.UInt)); + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt4), equals(ValueType.UInt)); + + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat2), equals(ValueType.Float)); + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat3), equals(ValueType.Float)); + expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat4), equals(ValueType.Float)); + }); + test('fixed typed vector element size', () { + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt2), equals(2)); + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt3), equals(3)); + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt4), equals(4)); + + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt2), equals(2)); + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt3), equals(3)); + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt4), equals(4)); + + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat2), equals(2)); + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat3), equals(3)); + expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat4), equals(4)); + }); + test('packed type', () { + expect(ValueTypeUtils.packedType(ValueType.Null, BitWidth.width8), equals(0)); + expect(ValueTypeUtils.packedType(ValueType.Null, BitWidth.width16), equals(1)); + expect(ValueTypeUtils.packedType(ValueType.Null, BitWidth.width32), equals(2)); + expect(ValueTypeUtils.packedType(ValueType.Null, BitWidth.width64), equals(3)); + + expect(ValueTypeUtils.packedType(ValueType.Int, BitWidth.width8), equals(4)); + expect(ValueTypeUtils.packedType(ValueType.Int, BitWidth.width16), equals(5)); + expect(ValueTypeUtils.packedType(ValueType.Int, BitWidth.width32), equals(6)); + expect(ValueTypeUtils.packedType(ValueType.Int, BitWidth.width64), equals(7)); + }); + test('bit width', () { + expect(BitWidthUtil.width(0), BitWidth.width8); + expect(BitWidthUtil.width(-20), BitWidth.width8); + expect(BitWidthUtil.width(127), BitWidth.width8); + expect(BitWidthUtil.width(128), BitWidth.width16); + expect(BitWidthUtil.width(128123), BitWidth.width32); + expect(BitWidthUtil.width(12812324534), BitWidth.width64); + expect(BitWidthUtil.width(-127), BitWidth.width8); + expect(BitWidthUtil.width(-128), BitWidth.width16); + expect(BitWidthUtil.width(-12812324534), BitWidth.width64); + expect(BitWidthUtil.width(-0.1), BitWidth.width64); + expect(BitWidthUtil.width(0.25), BitWidth.width32); + }); + test('padding size', () { + expect(BitWidthUtil.paddingSize(10, 8), 6); + expect(BitWidthUtil.paddingSize(10, 4), 2); + expect(BitWidthUtil.paddingSize(15, 4), 1); + expect(BitWidthUtil.paddingSize(15, 2), 1); + expect(BitWidthUtil.paddingSize(15, 1), 0); + expect(BitWidthUtil.paddingSize(16, 8), 0); + expect(BitWidthUtil.paddingSize(17, 8), 7); + }); +} |