| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:convert'; |
| import 'dart:typed_data'; |
| |
| /// Puts a buffer in front of a [Sink<List<int>>]. |
| class BufferedSink { |
| static const int _SIZE = 128 * 1024; |
| static const int _SAFE_LENGTH = _SIZE - 5; |
| |
| final BytesBuilder _builder = BytesBuilder(copy: false); |
| |
| Uint8List _buffer = Uint8List(_SIZE); |
| int _length = 0; |
| |
| final Int64List _int64Buffer = Int64List(1); |
| late final Uint8List _int64BufferUint8 = _int64Buffer.buffer.asUint8List(); |
| |
| final Float64List _doubleBuffer = Float64List(1); |
| late final Uint8List _doubleBufferUint8 = _doubleBuffer.buffer.asUint8List(); |
| |
| BufferedSink(); |
| |
| int get offset => _builder.length + _length; |
| |
| Uint8List takeBytes() { |
| _builder.add(_buffer.sublist(0, _length)); |
| return _builder.takeBytes(); |
| } |
| |
| @pragma("vm:prefer-inline") |
| void writeBool(bool value) { |
| writeByte(value ? 1 : 0); |
| } |
| |
| @pragma("vm:prefer-inline") |
| void writeByte(int byte) { |
| assert((byte & 0xFF) == byte); |
| _addByte(byte); |
| } |
| |
| void writeBytes(Uint8List bytes) { |
| if (bytes.isEmpty) { |
| return; |
| } |
| |
| // Usually the bytes is short, and fits the current buffer. |
| if (_length + bytes.length < _SIZE) { |
| _buffer.setRange(_length, _length + bytes.length, bytes); |
| _length += bytes.length; |
| return; |
| } |
| |
| // If the bytes is too long, add separate buffers. |
| if (bytes.length >= _SIZE) { |
| _builder.add(_buffer.sublist(0, _length)); |
| _builder.add(bytes); |
| // Start a new buffer. |
| _buffer = Uint8List(_SIZE); |
| _length = 0; |
| return; |
| } |
| |
| // Copy as much as we can into the current buffer. |
| _buffer.setRange(_length, _SIZE, bytes); |
| _builder.add(_buffer); |
| |
| // Copy the remainder into a new buffer. |
| var alreadyCopied = _SIZE - _length; |
| var remainder = bytes.length - alreadyCopied; |
| _buffer = Uint8List(_SIZE); |
| _buffer.setRange(0, remainder, bytes, alreadyCopied); |
| _length = remainder; |
| } |
| |
| void writeDouble(double value) { |
| _doubleBuffer[0] = value; |
| _addByte4( |
| _doubleBufferUint8[0], |
| _doubleBufferUint8[1], |
| _doubleBufferUint8[2], |
| _doubleBufferUint8[3], |
| ); |
| _addByte4( |
| _doubleBufferUint8[4], |
| _doubleBufferUint8[5], |
| _doubleBufferUint8[6], |
| _doubleBufferUint8[7], |
| ); |
| } |
| |
| void writeEnum(Enum e) { |
| writeByte(e.index); |
| } |
| |
| void writeIf<T extends Object>(bool condition, void Function() ifTrue) { |
| if (condition) { |
| writeBool(true); |
| ifTrue(); |
| } else { |
| writeBool(false); |
| } |
| } |
| |
| void writeIfType<T extends Object>( |
| Object? object, |
| void Function(T t) ifTrue, |
| ) { |
| if (object is T) { |
| writeBool(true); |
| ifTrue(object); |
| } else { |
| writeBool(false); |
| } |
| } |
| |
| void writeInt64(int value) { |
| _int64Buffer[0] = value; |
| _addByte4( |
| _int64BufferUint8[0], |
| _int64BufferUint8[1], |
| _int64BufferUint8[2], |
| _int64BufferUint8[3], |
| ); |
| _addByte4( |
| _int64BufferUint8[4], |
| _int64BufferUint8[5], |
| _int64BufferUint8[6], |
| _int64BufferUint8[7], |
| ); |
| } |
| |
| /// Writes [items], converts to [List] first. |
| void writeIterable<T>(Iterable<T> items, void Function(T x) writeItem) { |
| writeList(items.toList(), writeItem); |
| } |
| |
| void writeList<T>(List<T> items, void Function(T x) writeItem) { |
| writeUInt30(items.length); |
| for (var i = 0; i < items.length; i++) { |
| writeItem(items[i]); |
| } |
| } |
| |
| /// Write [items] filtering them by [T]. |
| void writeList2<T>(List<Object> items, void Function(T x) writeItem) { |
| var typedItems = items.whereType<T>().toList(); |
| writeUInt30(typedItems.length); |
| for (var i = 0; i < typedItems.length; i++) { |
| writeItem(typedItems[i]); |
| } |
| } |
| |
| void writeMap<K, V>( |
| Map<K, V> map, { |
| required void Function(K key) writeKey, |
| required void Function(V value) writeValue, |
| }) { |
| writeUInt30(map.length); |
| for (var entry in map.entries) { |
| writeKey(entry.key); |
| writeValue(entry.value); |
| } |
| } |
| |
| void writeOptionalInt64(int? value) { |
| if (value != null) { |
| writeBool(true); |
| writeInt64(value); |
| } else { |
| writeBool(false); |
| } |
| } |
| |
| void writeOptionalObject<T>(T? object, void Function(T x) write) { |
| if (object != null) { |
| writeBool(true); |
| write(object); |
| } else { |
| writeBool(false); |
| } |
| } |
| |
| void writeOptionalStringUtf8(String? value) { |
| if (value != null) { |
| writeBool(true); |
| writeStringUtf8(value); |
| } else { |
| writeBool(false); |
| } |
| } |
| |
| void writeOptionalUInt30(int? value) { |
| if (value != null) { |
| writeBool(true); |
| writeUInt30(value); |
| } else { |
| writeBool(false); |
| } |
| } |
| |
| /// Write the [value] as UTF8 encoded byte array. |
| void writeStringUtf8(String value) { |
| var bytes = const Utf8Encoder().convert(value); |
| writeUint8List(bytes); |
| } |
| |
| void writeStringUtf8Iterable(Iterable<String> items) { |
| writeUInt30(items.length); |
| for (var item in items) { |
| writeStringUtf8(item); |
| } |
| } |
| |
| @pragma("vm:prefer-inline") |
| void writeUInt30(int value) { |
| assert(value >= 0 && value >> 30 == 0); |
| if (value < 0x80) { |
| _addByte(value); |
| } else if (value < 0x4000) { |
| _addByte2((value >> 8) | 0x80, value & 0xFF); |
| } else { |
| _addByte4( |
| (value >> 24) | 0xC0, |
| (value >> 16) & 0xFF, |
| (value >> 8) & 0xFF, |
| value & 0xFF, |
| ); |
| } |
| } |
| |
| void writeUint30List(List<int> values) { |
| var length = values.length; |
| writeUInt30(length); |
| for (var i = 0; i < length; i++) { |
| writeUInt30(values[i]); |
| } |
| } |
| |
| void writeUInt32(int value) { |
| _addByte4( |
| (value >> 24) & 0xFF, |
| (value >> 16) & 0xFF, |
| (value >> 8) & 0xFF, |
| value & 0xFF, |
| ); |
| } |
| |
| void writeUint8List(Uint8List bytes) { |
| writeUInt30(bytes.length); |
| writeBytes(bytes); |
| } |
| |
| void writeUri(Uri uri) { |
| var uriStr = uri.toString(); |
| writeStringUtf8(uriStr); |
| } |
| |
| @pragma("vm:prefer-inline") |
| void _addByte(int byte) { |
| _buffer[_length++] = byte; |
| if (_length == _SIZE) { |
| _builder.add(_buffer); |
| _buffer = Uint8List(_SIZE); |
| _length = 0; |
| } |
| } |
| |
| @pragma("vm:prefer-inline") |
| void _addByte2(int byte1, int byte2) { |
| if (_length < _SAFE_LENGTH) { |
| _buffer[_length++] = byte1; |
| _buffer[_length++] = byte2; |
| } else { |
| _addByte(byte1); |
| _addByte(byte2); |
| } |
| } |
| |
| @pragma("vm:prefer-inline") |
| void _addByte4(int byte1, int byte2, int byte3, int byte4) { |
| if (_length < _SAFE_LENGTH) { |
| _buffer[_length++] = byte1; |
| _buffer[_length++] = byte2; |
| _buffer[_length++] = byte3; |
| _buffer[_length++] = byte4; |
| } else { |
| _addByte(byte1); |
| _addByte(byte2); |
| _addByte(byte3); |
| _addByte(byte4); |
| } |
| } |
| } |